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.
- package/.vscode/settings.json +22 -0
- package/SECURITY.md +12 -0
- package/SPONSORS.md +1 -0
- package/build/build-validation.js +1 -1
- package/build/sync-version.js +1 -0
- package/docs/Guides/Ecosystem.md +5 -0
- package/docs/Guides/Fluent-Schema.md +2 -2
- package/docs/Reference/Decorators.md +36 -169
- package/docs/Reference/Encapsulation.md +5 -1
- package/docs/Reference/Plugins.md +11 -2
- package/docs/Reference/Reply.md +4 -1
- package/docs/Reference/Server.md +39 -1
- package/docs/Reference/Type-Providers.md +2 -2
- package/docs/Reference/TypeScript.md +136 -0
- package/eslint.config.js +18 -2
- package/fastify.d.ts +1 -1
- package/fastify.js +183 -168
- package/lib/{contentTypeParser.js → content-type-parser.js} +2 -1
- package/lib/error-handler.js +2 -2
- package/lib/{fourOhFour.js → four-oh-four.js} +4 -2
- package/lib/{handleRequest.js → handle-request.js} +1 -1
- package/lib/{headRoute.js → head-route.js} +13 -1
- package/lib/{initialConfigValidation.js → initial-config-validation.js} +1 -1
- package/lib/{pluginOverride.js → plugin-override.js} +2 -2
- package/lib/{pluginUtils.js → plugin-utils.js} +2 -2
- package/lib/reply.js +5 -3
- package/lib/request.js +5 -0
- package/lib/route.js +20 -9
- package/lib/server.js +94 -8
- package/lib/symbols.js +1 -0
- package/lib/validation.js +9 -1
- package/lib/warnings.js +1 -1
- package/package.json +8 -8
- package/test/500s.test.js +191 -0
- package/test/child-logger-factory.test.js +3 -3
- package/test/content-parser.test.js +2 -1
- package/test/decorator-namespace.test._js_ +1 -1
- package/test/diagnostics-channel/error-before-handler.test.js +1 -1
- package/test/http2/closing.test.js +88 -0
- package/test/internals/content-type-parser.test.js +2 -2
- package/test/internals/handle-request.test.js +2 -2
- package/test/internals/initial-config.test.js +1 -1
- package/test/internals/plugin.test.js +2 -2
- package/test/internals/reply.test.js +22 -3
- package/test/internals/req-id-gen-factory.test.js +1 -1
- package/test/promises.test.js +3 -3
- package/test/reply-web-stream-locked.test.js +37 -0
- package/test/request-error.test.js +116 -0
- package/test/route.6.test.js +20 -1
- package/test/route.7.test.js +49 -0
- package/test/schema-validation.test.js +27 -4
- package/test/server.test.js +22 -4
- package/test/set-error-handler.test.js +1 -1
- package/test/skip-reply-send.test.js +2 -2
- package/test/stream.5.test.js +3 -3
- package/test/types/fastify.test-d.ts +70 -18
- package/test/types/hooks.test-d.ts +6 -1
- package/test/types/instance.test-d.ts +35 -15
- package/test/types/logger.test-d.ts +18 -6
- package/test/types/plugin.test-d.ts +24 -6
- package/test/types/register.test-d.ts +108 -33
- package/test/types/reply.test-d.ts +23 -6
- package/test/types/request.test-d.ts +25 -6
- package/test/types/route.test-d.ts +10 -1
- package/test/types/schema.test-d.ts +21 -0
- package/test/validation-error-handling.test.js +68 -1
- package/test/wrap-thenable.test.js +1 -1
- package/types/instance.d.ts +2 -2
- package/types/schema.d.ts +1 -1
- package/test/check.test.js +0 -219
- /package/lib/{configValidator.js → config-validator.js} +0 -0
- /package/lib/{reqIdGenFactory.js → req-id-gen-factory.js} +0 -0
- /package/lib/{wrapThenable.js → wrap-thenable.js} +0 -0
- /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/
|
|
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 => {
|
package/types/instance.d.ts
CHANGED
|
@@ -464,12 +464,12 @@ export interface FastifyInstance<
|
|
|
464
464
|
/**
|
|
465
465
|
* Fastify default error handler
|
|
466
466
|
*/
|
|
467
|
-
errorHandler: (error:
|
|
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
|
|
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
|
|
package/test/check.test.js
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|