fastify 5.6.0 → 5.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.vscode/settings.json +22 -0
  2. package/SECURITY.md +12 -0
  3. package/SPONSORS.md +1 -0
  4. package/build/build-validation.js +1 -1
  5. package/build/sync-version.js +1 -0
  6. package/docs/Guides/Ecosystem.md +5 -0
  7. package/docs/Guides/Fluent-Schema.md +2 -2
  8. package/docs/Reference/Decorators.md +36 -169
  9. package/docs/Reference/Encapsulation.md +5 -1
  10. package/docs/Reference/Plugins.md +11 -2
  11. package/docs/Reference/Reply.md +4 -1
  12. package/docs/Reference/Server.md +39 -1
  13. package/docs/Reference/Type-Providers.md +2 -2
  14. package/docs/Reference/TypeScript.md +136 -0
  15. package/eslint.config.js +18 -2
  16. package/fastify.d.ts +1 -1
  17. package/fastify.js +183 -168
  18. package/lib/{contentTypeParser.js → content-type-parser.js} +2 -1
  19. package/lib/error-handler.js +2 -2
  20. package/lib/{fourOhFour.js → four-oh-four.js} +4 -2
  21. package/lib/{handleRequest.js → handle-request.js} +1 -1
  22. package/lib/{headRoute.js → head-route.js} +13 -1
  23. package/lib/{initialConfigValidation.js → initial-config-validation.js} +1 -1
  24. package/lib/{pluginOverride.js → plugin-override.js} +2 -2
  25. package/lib/{pluginUtils.js → plugin-utils.js} +2 -2
  26. package/lib/reply.js +5 -3
  27. package/lib/request.js +5 -0
  28. package/lib/route.js +20 -9
  29. package/lib/server.js +94 -8
  30. package/lib/symbols.js +1 -0
  31. package/lib/validation.js +9 -1
  32. package/lib/warnings.js +1 -1
  33. package/package.json +8 -8
  34. package/test/500s.test.js +191 -0
  35. package/test/child-logger-factory.test.js +3 -3
  36. package/test/content-parser.test.js +2 -1
  37. package/test/decorator-namespace.test._js_ +1 -1
  38. package/test/diagnostics-channel/error-before-handler.test.js +1 -1
  39. package/test/http2/closing.test.js +88 -0
  40. package/test/internals/content-type-parser.test.js +2 -2
  41. package/test/internals/handle-request.test.js +2 -2
  42. package/test/internals/initial-config.test.js +1 -1
  43. package/test/internals/plugin.test.js +2 -2
  44. package/test/internals/reply.test.js +22 -3
  45. package/test/internals/req-id-gen-factory.test.js +1 -1
  46. package/test/promises.test.js +3 -3
  47. package/test/reply-web-stream-locked.test.js +37 -0
  48. package/test/request-error.test.js +116 -0
  49. package/test/route.6.test.js +20 -1
  50. package/test/route.7.test.js +49 -0
  51. package/test/schema-validation.test.js +27 -4
  52. package/test/server.test.js +22 -4
  53. package/test/set-error-handler.test.js +1 -1
  54. package/test/skip-reply-send.test.js +2 -2
  55. package/test/stream.5.test.js +3 -3
  56. package/test/types/fastify.test-d.ts +70 -18
  57. package/test/types/hooks.test-d.ts +6 -1
  58. package/test/types/instance.test-d.ts +35 -15
  59. package/test/types/logger.test-d.ts +18 -6
  60. package/test/types/plugin.test-d.ts +24 -6
  61. package/test/types/register.test-d.ts +108 -33
  62. package/test/types/reply.test-d.ts +23 -6
  63. package/test/types/request.test-d.ts +25 -6
  64. package/test/types/route.test-d.ts +10 -1
  65. package/test/types/schema.test-d.ts +21 -0
  66. package/test/validation-error-handling.test.js +68 -1
  67. package/test/wrap-thenable.test.js +1 -1
  68. package/types/instance.d.ts +2 -2
  69. package/types/schema.d.ts +1 -1
  70. package/test/check.test.js +0 -219
  71. /package/lib/{configValidator.js → config-validator.js} +0 -0
  72. /package/lib/{reqIdGenFactory.js → req-id-gen-factory.js} +0 -0
  73. /package/lib/{wrapThenable.js → wrap-thenable.js} +0 -0
  74. /package/types/{serverFactory.d.ts → server-factory.d.ts} +0 -0
@@ -72,6 +72,27 @@ expectAssignable<FastifyInstance>(server.post('/test', {
72
72
  }
73
73
  }, async req => req.body))
74
74
 
75
+ expectAssignable<FastifyInstance>(server.post('/test', {
76
+ validatorCompiler: ({ schema }) => {
77
+ return data => {
78
+ if (!data || data.constructor !== Object) {
79
+ return {
80
+ error: [
81
+ {
82
+ keyword: 'type',
83
+ instancePath: '',
84
+ schemaPath: '#/type',
85
+ params: { type: 'object' },
86
+ message: 'value is not an object'
87
+ }
88
+ ]
89
+ }
90
+ }
91
+ return { value: data }
92
+ }
93
+ }
94
+ }, async req => req.body))
95
+
75
96
  expectAssignable<FastifyInstance>(server.setValidatorCompiler<FastifySchema & { validate: Record<string, unknown> }>(
76
97
  function ({ schema }) {
77
98
  return new Ajv().compile(schema)
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const { test } = require('node:test')
3
+ const { describe, test } = require('node:test')
4
4
  const Joi = require('joi')
5
5
  const Fastify = require('..')
6
6
 
@@ -831,3 +831,70 @@ test('plugin override', async (t) => {
831
831
  })
832
832
  t.assert.strictEqual(response5.statusCode, 400)
833
833
  })
834
+
835
+ describe('sync and async must work in the same way', () => {
836
+ // Route with custom validator that throws
837
+ const throwingRouteValidator = {
838
+ schema: {
839
+ body: {
840
+ type: 'object',
841
+ properties: { name: { type: 'string' } }
842
+ }
843
+ },
844
+ validatorCompiler: () => {
845
+ return function (inputData) {
846
+ // This custom validator throws a sync error instead of returning `{ error }`
847
+ throw new Error('Custom validation failed')
848
+ }
849
+ },
850
+ handler (request, reply) { reply.send({ success: true }) }
851
+ }
852
+
853
+ test('async preValidation with custom validator should trigger error handler when validator throws', async (t) => {
854
+ t.plan(4)
855
+
856
+ const fastify = Fastify()
857
+ fastify.setErrorHandler((error, request, reply) => {
858
+ t.assert.ok(error instanceof Error, 'error should be an Error instance')
859
+ t.assert.strictEqual(error.message, 'Custom validation failed')
860
+ reply.status(500).send({ error: error.message })
861
+ })
862
+
863
+ // Add async preValidation hook
864
+ fastify.addHook('preValidation', async (request, reply) => {
865
+ await Promise.resolve('ok')
866
+ })
867
+ fastify.post('/async', throwingRouteValidator)
868
+
869
+ const response = await fastify.inject({
870
+ method: 'POST',
871
+ url: '/async',
872
+ payload: { name: 'test' }
873
+ })
874
+ t.assert.strictEqual(response.statusCode, 500)
875
+ t.assert.deepStrictEqual(response.json(), { error: 'Custom validation failed' })
876
+ })
877
+
878
+ test('sync preValidation with custom validator should trigger error handler when validator throws', async (t) => {
879
+ t.plan(4)
880
+
881
+ const fastify = Fastify()
882
+ fastify.setErrorHandler((error, request, reply) => {
883
+ t.assert.ok(error instanceof Error, 'error should be an Error instance')
884
+ t.assert.strictEqual(error.message, 'Custom validation failed')
885
+ reply.status(500).send({ error: error.message })
886
+ })
887
+
888
+ // Add sync preValidation hook
889
+ fastify.addHook('preValidation', (request, reply, next) => { next() })
890
+ fastify.post('/sync', throwingRouteValidator)
891
+
892
+ const response = await fastify.inject({
893
+ method: 'POST',
894
+ url: '/sync',
895
+ payload: { name: 'test' }
896
+ })
897
+ t.assert.strictEqual(response.statusCode, 500)
898
+ t.assert.deepStrictEqual(response.json(), { error: 'Custom validation failed' })
899
+ })
900
+ })
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { test } = require('node:test')
4
4
  const { kReplyHijacked } = require('../lib/symbols')
5
- const wrapThenable = require('../lib/wrapThenable')
5
+ const wrapThenable = require('../lib/wrap-thenable')
6
6
  const Reply = require('../lib/reply')
7
7
 
8
8
  test('should resolve immediately when reply[kReplyHijacked] is true', async t => {
@@ -464,12 +464,12 @@ export interface FastifyInstance<
464
464
  /**
465
465
  * Fastify default error handler
466
466
  */
467
- errorHandler: (error: FastifyError, request: FastifyRequest, reply: FastifyReply) => void;
467
+ errorHandler: <TError = unknown>(error: TError, request: FastifyRequest, reply: FastifyReply) => void;
468
468
 
469
469
  /**
470
470
  * Set a function that will be invoked whenever an exception is thrown during the request lifecycle.
471
471
  */
472
- setErrorHandler<TError extends Error = FastifyError, RouteGeneric extends RouteGenericInterface = RouteGenericInterface, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault>(
472
+ setErrorHandler<TError = unknown, RouteGeneric extends RouteGenericInterface = RouteGenericInterface, SchemaCompiler extends FastifySchema = FastifySchema, TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault>(
473
473
  handler: (this: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>, error: TError, request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, reply: FastifyReply<RouteGeneric, RawServer, RawRequest, RawReply, ContextConfigDefault, SchemaCompiler, TypeProvider>) => any | Promise<any>
474
474
  ): FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>;
475
475
 
package/types/schema.d.ts CHANGED
@@ -33,7 +33,7 @@ export interface FastifySchemaValidationError {
33
33
  }
34
34
 
35
35
  export interface FastifyValidationResult {
36
- (data: any): boolean | SafePromiseLike<any> | { error?: Error, value?: any }
36
+ (data: any): boolean | SafePromiseLike<any> | { error?: Error | FastifySchemaValidationError[], value?: any }
37
37
  errors?: FastifySchemaValidationError[] | null;
38
38
  }
39
39
 
@@ -1,219 +0,0 @@
1
- 'use strict'
2
-
3
- const { test } = require('node:test')
4
- const { S } = require('fluent-json-schema')
5
- const Fastify = require('..')
6
-
7
- const BadRequestSchema = S.object()
8
- .prop('statusCode', S.number())
9
- .prop('error', S.string())
10
- .prop('message', S.string())
11
-
12
- const InternalServerErrorSchema = S.object()
13
- .prop('statusCode', S.number())
14
- .prop('error', S.string())
15
- .prop('message', S.string())
16
-
17
- const NotFoundSchema = S.object()
18
- .prop('statusCode', S.number())
19
- .prop('error', S.string())
20
- .prop('message', S.string())
21
-
22
- const options = {
23
- schema: {
24
- body: {
25
- type: 'object',
26
- properties: {
27
- id: { type: 'string' }
28
- }
29
- },
30
- response: {
31
- 200: {
32
- type: 'object',
33
- properties: {
34
- id: { type: 'string' }
35
- }
36
- },
37
- 400: {
38
- description: 'Bad Request',
39
- content: {
40
- 'application/json': {
41
- schema: BadRequestSchema.valueOf()
42
- }
43
- }
44
- },
45
- 404: {
46
- description: 'Resource not found',
47
- content: {
48
- 'application/json': {
49
- schema: NotFoundSchema.valueOf(),
50
- example: {
51
- statusCode: 404,
52
- error: 'Not Found',
53
- message: 'Not Found'
54
- }
55
- }
56
- }
57
- },
58
- 500: {
59
- description: 'Internal Server Error',
60
- content: {
61
- 'application/json': {
62
- schema: InternalServerErrorSchema.valueOf(),
63
- example: {
64
- message: 'Internal Server Error'
65
- }
66
- }
67
- }
68
- }
69
- }
70
- }
71
- }
72
-
73
- const handler = (request, reply) => {
74
- if (request.body.id === '400') {
75
- return reply.status(400).send({
76
- statusCode: 400,
77
- error: 'Bad Request',
78
- message: 'Custom message',
79
- extra: 'This should not be in the response'
80
- })
81
- }
82
-
83
- if (request.body.id === '404') {
84
- return reply.status(404).send({
85
- statusCode: 404,
86
- error: 'Not Found',
87
- message: 'Custom Not Found',
88
- extra: 'This should not be in the response'
89
- })
90
- }
91
-
92
- if (request.body.id === '500') {
93
- reply.status(500).send({
94
- statusCode: 500,
95
- error: 'Internal Server Error',
96
- message: 'Custom Internal Server Error',
97
- extra: 'This should not be in the response'
98
- })
99
- }
100
-
101
- reply.send({
102
- id: request.body.id,
103
- extra: 'This should not be in the response'
104
- })
105
- }
106
-
107
- test('serialize the response for a Bad Request error, as defined on the schema', async t => {
108
- const fastify = Fastify({})
109
-
110
- t.after(() => fastify.close())
111
-
112
- fastify.post('/', options, handler)
113
-
114
- const fastifyServer = await fastify.listen({ port: 0 })
115
-
116
- const result = await fetch(fastifyServer, {
117
- method: 'POST',
118
- headers: {
119
- 'Content-Type': 'application/json'
120
- },
121
- body: '12'
122
- })
123
-
124
- t.assert.ok(!result.ok)
125
- t.assert.strictEqual(result.status, 400)
126
- t.assert.deepStrictEqual(await result.json(), {
127
- statusCode: 400,
128
- error: 'Bad Request',
129
- message: 'body must be object'
130
- })
131
- })
132
-
133
- // test('serialize the response for a Not Found error, as defined on the schema', t => {
134
- // const fastify = Fastify({})
135
-
136
- // t.teardown(fastify.close.bind(fastify))
137
-
138
- // fastify.post('/', options, handler)
139
-
140
- // fastify.listen({ port: 0 }, err => {
141
- // t.error(err)
142
-
143
- // const url = `http://localhost:${fastify.server.address().port}/`
144
-
145
- // sget({
146
- // method: 'POST',
147
- // url,
148
- // json: true,
149
- // body: { id: '404' }
150
- // }, (err, response, body) => {
151
- // t.error(err)
152
- // t.equal(response.statusCode, 404)
153
- // t.same(body, {
154
- // statusCode: 404,
155
- // error: 'Not Found',
156
- // message: 'Custom Not Found'
157
- // })
158
- // t.end()
159
- // })
160
- // })
161
- // })
162
-
163
- // test('serialize the response for a Internal Server Error error, as defined on the schema', t => {
164
- // const fastify = Fastify({})
165
-
166
- // t.teardown(fastify.close.bind(fastify))
167
-
168
- // fastify.post('/', options, handler)
169
-
170
- // fastify.listen({ port: 0 }, err => {
171
- // t.error(err)
172
-
173
- // const url = `http://localhost:${fastify.server.address().port}/`
174
-
175
- // sget({
176
- // method: 'POST',
177
- // url,
178
- // json: true,
179
- // body: { id: '500' }
180
- // }, (err, response, body) => {
181
- // t.error(err)
182
- // t.equal(response.statusCode, 500)
183
- // t.same(body, {
184
- // statusCode: 500,
185
- // error: 'Internal Server Error',
186
- // message: 'Custom Internal Server Error'
187
- // })
188
- // t.end()
189
- // })
190
- // })
191
- // })
192
-
193
- // test('serialize the success response, as defined on the schema', t => {
194
- // const fastify = Fastify({})
195
-
196
- // t.teardown(fastify.close.bind(fastify))
197
-
198
- // fastify.post('/', options, handler)
199
-
200
- // fastify.listen({ port: 0 }, err => {
201
- // t.error(err)
202
-
203
- // const url = `http://localhost:${fastify.server.address().port}/`
204
-
205
- // sget({
206
- // method: 'POST',
207
- // url,
208
- // json: true,
209
- // body: { id: 'test' }
210
- // }, (err, response, body) => {
211
- // t.error(err)
212
- // t.equal(response.statusCode, 200)
213
- // t.same(body, {
214
- // id: 'test'
215
- // })
216
- // t.end()
217
- // })
218
- // })
219
- // })
File without changes
File without changes