fastify 3.26.0 → 4.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/build/build-error-serializer.js +27 -0
- package/build/build-validation.js +49 -35
- package/docs/Guides/Ecosystem.md +2 -1
- package/docs/Guides/Prototype-Poisoning.md +3 -3
- package/docs/Migration-Guide-V4.md +12 -0
- package/docs/Reference/ContentTypeParser.md +8 -1
- package/docs/Reference/Errors.md +51 -6
- package/docs/Reference/Hooks.md +4 -7
- package/docs/Reference/LTS.md +5 -4
- package/docs/Reference/Reply.md +23 -22
- package/docs/Reference/Request.md +1 -3
- package/docs/Reference/Routes.md +17 -10
- package/docs/Reference/Server.md +98 -63
- package/docs/Reference/TypeScript.md +11 -13
- package/docs/Reference/Validation-and-Serialization.md +32 -54
- package/docs/Type-Providers.md +257 -0
- package/examples/hooks.js +1 -1
- package/examples/simple-stream.js +18 -0
- package/fastify.d.ts +36 -22
- package/fastify.js +72 -53
- package/lib/configValidator.js +902 -1023
- package/lib/contentTypeParser.js +6 -16
- package/lib/context.js +36 -10
- package/lib/decorate.js +5 -3
- package/lib/error-handler.js +158 -0
- package/lib/error-serializer.js +257 -0
- package/lib/errors.js +49 -10
- package/lib/fourOhFour.js +31 -20
- package/lib/handleRequest.js +10 -13
- package/lib/hooks.js +14 -9
- package/lib/noop-set.js +10 -0
- package/lib/pluginOverride.js +0 -3
- package/lib/pluginUtils.js +3 -2
- package/lib/reply.js +44 -163
- package/lib/request.js +13 -10
- package/lib/route.js +158 -139
- package/lib/schema-controller.js +3 -3
- package/lib/schemas.js +27 -1
- package/lib/server.js +219 -116
- package/lib/symbols.js +6 -4
- package/lib/validation.js +2 -1
- package/lib/warnings.js +2 -12
- package/lib/wrapThenable.js +4 -11
- package/package.json +40 -45
- package/test/404s.test.js +265 -108
- package/test/500s.test.js +2 -2
- package/test/async-await.test.js +15 -71
- package/test/close.test.js +39 -1
- package/test/content-parser.test.js +32 -0
- package/test/context-config.test.js +56 -4
- package/test/custom-http-server.test.js +14 -7
- package/test/custom-parser-async.test.js +0 -65
- package/test/custom-parser.test.js +54 -121
- package/test/decorator.test.js +1 -3
- package/test/delete.test.js +5 -5
- package/test/encapsulated-error-handler.test.js +50 -0
- package/test/esm/index.test.js +0 -14
- package/test/fastify-instance.test.js +4 -4
- package/test/fluent-schema.test.js +4 -4
- package/test/get.test.js +3 -3
- package/test/helper.js +18 -3
- package/test/hooks-async.test.js +14 -47
- package/test/hooks.on-ready.test.js +9 -4
- package/test/hooks.test.js +58 -99
- package/test/http2/closing.test.js +5 -11
- package/test/http2/unknown-http-method.test.js +3 -9
- package/test/https/custom-https-server.test.js +12 -6
- package/test/inject.test.js +1 -1
- package/test/input-validation.js +2 -2
- package/test/internals/all.test.js +2 -2
- package/test/internals/contentTypeParser.test.js +4 -4
- package/test/internals/handleRequest.test.js +9 -46
- package/test/internals/initialConfig.test.js +33 -12
- package/test/internals/logger.test.js +1 -1
- package/test/internals/reply.test.js +245 -3
- package/test/internals/request.test.js +13 -7
- package/test/internals/server.test.js +88 -0
- package/test/listen.test.js +84 -1
- package/test/logger.test.js +98 -58
- package/test/maxRequestsPerSocket.test.js +8 -6
- package/test/middleware.test.js +2 -25
- package/test/noop-set.test.js +19 -0
- package/test/nullable-validation.test.js +51 -14
- package/test/plugin.test.js +31 -5
- package/test/pretty-print.test.js +22 -10
- package/test/reply-error.test.js +123 -12
- package/test/request-error.test.js +2 -5
- package/test/route-hooks.test.js +17 -17
- package/test/route-prefix.test.js +2 -1
- package/test/route.test.js +216 -20
- package/test/router-options.test.js +1 -1
- package/test/schema-examples.test.js +11 -5
- package/test/schema-feature.test.js +24 -19
- package/test/schema-serialization.test.js +50 -9
- package/test/schema-special-usage.test.js +14 -81
- package/test/schema-validation.test.js +9 -9
- package/test/skip-reply-send.test.js +8 -8
- package/test/stream.test.js +23 -12
- package/test/throw.test.js +8 -5
- package/test/trust-proxy.test.js +1 -1
- package/test/type-provider.test.js +20 -0
- package/test/types/fastify.test-d.ts +12 -18
- package/test/types/hooks.test-d.ts +7 -3
- package/test/types/import.js +2 -0
- package/test/types/import.ts +1 -0
- package/test/types/instance.test-d.ts +61 -15
- package/test/types/logger.test-d.ts +44 -15
- package/test/types/route.test-d.ts +8 -2
- package/test/types/schema.test-d.ts +2 -39
- package/test/types/type-provider.test-d.ts +417 -0
- package/test/validation-error-handling.test.js +9 -9
- package/test/versioned-routes.test.js +29 -17
- package/test/wrapThenable.test.js +7 -6
- package/types/.eslintrc.json +1 -1
- package/types/content-type-parser.d.ts +17 -8
- package/types/hooks.d.ts +107 -60
- package/types/instance.d.ts +137 -105
- package/types/logger.d.ts +18 -104
- package/types/plugin.d.ts +10 -4
- package/types/register.d.ts +1 -1
- package/types/reply.d.ts +16 -11
- package/types/request.d.ts +10 -5
- package/types/route.d.ts +42 -31
- package/types/schema.d.ts +15 -1
- package/types/type-provider.d.ts +99 -0
- package/types/utils.d.ts +1 -1
- package/lib/schema-compilers.js +0 -12
- package/test/emit-warning.test.js +0 -166
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import fastify, { FastifyTypeProvider } from '../../fastify'
|
|
2
|
+
import { expectAssignable, expectError, expectType } from 'tsd'
|
|
3
|
+
import { IncomingHttpHeaders } from 'http'
|
|
4
|
+
import { Type, TSchema, Static } from '@sinclair/typebox'
|
|
5
|
+
import { FromSchema, JSONSchema } from 'json-schema-to-ts'
|
|
6
|
+
|
|
7
|
+
const server = fastify()
|
|
8
|
+
|
|
9
|
+
// -------------------------------------------------------------------
|
|
10
|
+
// Remapping
|
|
11
|
+
// -------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
interface NumberProvider extends FastifyTypeProvider { output: number } // remap all schemas to numbers
|
|
14
|
+
|
|
15
|
+
expectAssignable(server.withTypeProvider<NumberProvider>().get(
|
|
16
|
+
'/',
|
|
17
|
+
{
|
|
18
|
+
schema: {
|
|
19
|
+
body: { type: 'string' },
|
|
20
|
+
querystring: { type: 'string' },
|
|
21
|
+
headers: { type: 'string' },
|
|
22
|
+
params: { type: 'string' }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
(req) => {
|
|
26
|
+
expectType<number & IncomingHttpHeaders>(req.headers)
|
|
27
|
+
expectType<number>(req.body)
|
|
28
|
+
expectType<number>(req.query)
|
|
29
|
+
expectType<number>(req.params)
|
|
30
|
+
}
|
|
31
|
+
))
|
|
32
|
+
|
|
33
|
+
// -------------------------------------------------------------------
|
|
34
|
+
// Override
|
|
35
|
+
// -------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
interface OverriddenProvider extends FastifyTypeProvider { output: 'inferenced' }
|
|
38
|
+
|
|
39
|
+
expectAssignable(server.withTypeProvider<OverriddenProvider>().get<{ Body: 'override' }>(
|
|
40
|
+
'/',
|
|
41
|
+
{
|
|
42
|
+
schema: {
|
|
43
|
+
body: Type.Object({
|
|
44
|
+
x: Type.Number(),
|
|
45
|
+
y: Type.Number(),
|
|
46
|
+
z: Type.Number()
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
(req) => {
|
|
51
|
+
expectType<'override'>(req.body)
|
|
52
|
+
}
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
// -------------------------------------------------------------------
|
|
56
|
+
// TypeBox
|
|
57
|
+
// -------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
interface TypeBoxProvider extends FastifyTypeProvider { output: this['input'] extends TSchema ? Static<this['input']> : never }
|
|
60
|
+
|
|
61
|
+
expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
|
|
62
|
+
'/',
|
|
63
|
+
{
|
|
64
|
+
schema: {
|
|
65
|
+
body: Type.Object({
|
|
66
|
+
x: Type.Number(),
|
|
67
|
+
y: Type.Number(),
|
|
68
|
+
z: Type.Number()
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
(req) => {
|
|
73
|
+
expectType<number>(req.body.x)
|
|
74
|
+
expectType<number>(req.body.y)
|
|
75
|
+
expectType<number>(req.body.z)
|
|
76
|
+
}
|
|
77
|
+
))
|
|
78
|
+
|
|
79
|
+
// -------------------------------------------------------------------
|
|
80
|
+
// JsonSchemaToTs
|
|
81
|
+
// -------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
interface JsonSchemaToTsProvider extends FastifyTypeProvider { output: this['input'] extends JSONSchema ? FromSchema<this['input']> : never }
|
|
84
|
+
|
|
85
|
+
expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
|
|
86
|
+
'/',
|
|
87
|
+
{
|
|
88
|
+
schema: {
|
|
89
|
+
body: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
x: { type: 'number' },
|
|
93
|
+
y: { type: 'string' },
|
|
94
|
+
z: { type: 'boolean' }
|
|
95
|
+
}
|
|
96
|
+
} as const
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
(req) => {
|
|
100
|
+
expectType<number | undefined>(req.body.x)
|
|
101
|
+
expectType<string | undefined>(req.body.y)
|
|
102
|
+
expectType<boolean | undefined>(req.body.z)
|
|
103
|
+
}
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
// -------------------------------------------------------------------
|
|
107
|
+
// Instance Type Remappable
|
|
108
|
+
// -------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
expectAssignable(server.withTypeProvider<TypeBoxProvider>().withTypeProvider<JsonSchemaToTsProvider>().get(
|
|
111
|
+
'/',
|
|
112
|
+
{
|
|
113
|
+
schema: {
|
|
114
|
+
body: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
x: { type: 'number' },
|
|
118
|
+
y: { type: 'string' },
|
|
119
|
+
z: { type: 'boolean' }
|
|
120
|
+
}
|
|
121
|
+
} as const
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
(req) => {
|
|
125
|
+
expectType<number | undefined>(req.body.x)
|
|
126
|
+
expectType<string | undefined>(req.body.y)
|
|
127
|
+
expectType<boolean | undefined>(req.body.z)
|
|
128
|
+
}
|
|
129
|
+
))
|
|
130
|
+
|
|
131
|
+
// -------------------------------------------------------------------
|
|
132
|
+
// Request Hooks
|
|
133
|
+
// -------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
|
|
136
|
+
'/',
|
|
137
|
+
{
|
|
138
|
+
schema: {
|
|
139
|
+
body: Type.Object({
|
|
140
|
+
x: Type.Number(),
|
|
141
|
+
y: Type.String(),
|
|
142
|
+
z: Type.Boolean()
|
|
143
|
+
})
|
|
144
|
+
},
|
|
145
|
+
preHandler: req => {
|
|
146
|
+
expectType<number>(req.body.x)
|
|
147
|
+
expectType<string>(req.body.y)
|
|
148
|
+
expectType<boolean>(req.body.z)
|
|
149
|
+
},
|
|
150
|
+
preParsing: req => {
|
|
151
|
+
expectType<number>(req.body.x)
|
|
152
|
+
expectType<string>(req.body.y)
|
|
153
|
+
expectType<boolean>(req.body.z)
|
|
154
|
+
},
|
|
155
|
+
preSerialization: req => {
|
|
156
|
+
expectType<number>(req.body.x)
|
|
157
|
+
expectType<string>(req.body.y)
|
|
158
|
+
expectType<boolean>(req.body.z)
|
|
159
|
+
},
|
|
160
|
+
preValidation: req => {
|
|
161
|
+
expectType<number>(req.body.x)
|
|
162
|
+
expectType<string>(req.body.y)
|
|
163
|
+
expectType<boolean>(req.body.z)
|
|
164
|
+
},
|
|
165
|
+
onError: req => {
|
|
166
|
+
expectType<number>(req.body.x)
|
|
167
|
+
expectType<string>(req.body.y)
|
|
168
|
+
expectType<boolean>(req.body.z)
|
|
169
|
+
},
|
|
170
|
+
onRequest: req => {
|
|
171
|
+
expectType<number>(req.body.x)
|
|
172
|
+
expectType<string>(req.body.y)
|
|
173
|
+
expectType<boolean>(req.body.z)
|
|
174
|
+
},
|
|
175
|
+
onResponse: req => {
|
|
176
|
+
expectType<number>(req.body.x)
|
|
177
|
+
expectType<string>(req.body.y)
|
|
178
|
+
expectType<boolean>(req.body.z)
|
|
179
|
+
},
|
|
180
|
+
onTimeout: req => {
|
|
181
|
+
expectType<number>(req.body.x)
|
|
182
|
+
expectType<string>(req.body.y)
|
|
183
|
+
expectType<boolean>(req.body.z)
|
|
184
|
+
},
|
|
185
|
+
onSend: req => {
|
|
186
|
+
expectType<number>(req.body.x)
|
|
187
|
+
expectType<string>(req.body.y)
|
|
188
|
+
expectType<boolean>(req.body.z)
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
req => {
|
|
192
|
+
expectType<number>(req.body.x)
|
|
193
|
+
expectType<string>(req.body.y)
|
|
194
|
+
expectType<boolean>(req.body.z)
|
|
195
|
+
}
|
|
196
|
+
))
|
|
197
|
+
|
|
198
|
+
// -------------------------------------------------------------------
|
|
199
|
+
// TypeBox Reply Type
|
|
200
|
+
// -------------------------------------------------------------------
|
|
201
|
+
|
|
202
|
+
expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
|
|
203
|
+
'/',
|
|
204
|
+
{
|
|
205
|
+
schema: {
|
|
206
|
+
response: {
|
|
207
|
+
200: Type.String(),
|
|
208
|
+
400: Type.Number(),
|
|
209
|
+
500: Type.Object({
|
|
210
|
+
error: Type.String()
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
async (_, res) => {
|
|
216
|
+
res.send('hello')
|
|
217
|
+
res.send(42)
|
|
218
|
+
res.send({ error: 'error' })
|
|
219
|
+
}
|
|
220
|
+
))
|
|
221
|
+
|
|
222
|
+
// -------------------------------------------------------------------
|
|
223
|
+
// TypeBox Reply Type: Non Assignable
|
|
224
|
+
// -------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
expectError(server.withTypeProvider<TypeBoxProvider>().get(
|
|
227
|
+
'/',
|
|
228
|
+
{
|
|
229
|
+
schema: {
|
|
230
|
+
response: {
|
|
231
|
+
200: Type.String(),
|
|
232
|
+
400: Type.Number(),
|
|
233
|
+
500: Type.Object({
|
|
234
|
+
error: Type.String()
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
async (_, res) => {
|
|
240
|
+
res.send(false)
|
|
241
|
+
}
|
|
242
|
+
))
|
|
243
|
+
|
|
244
|
+
// -------------------------------------------------------------------
|
|
245
|
+
// TypeBox Reply Return Type
|
|
246
|
+
// -------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
|
|
249
|
+
'/',
|
|
250
|
+
{
|
|
251
|
+
schema: {
|
|
252
|
+
response: {
|
|
253
|
+
200: Type.String(),
|
|
254
|
+
400: Type.Number(),
|
|
255
|
+
500: Type.Object({
|
|
256
|
+
error: Type.String()
|
|
257
|
+
})
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
async (_, res) => {
|
|
262
|
+
const option = 1 as 1 | 2 | 3
|
|
263
|
+
switch (option) {
|
|
264
|
+
case 1: return 'hello'
|
|
265
|
+
case 2: return 42
|
|
266
|
+
case 3: return { error: 'error' }
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
))
|
|
270
|
+
|
|
271
|
+
// -------------------------------------------------------------------
|
|
272
|
+
// TypeBox Reply Return Type: Non Assignable
|
|
273
|
+
// -------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
expectError(server.withTypeProvider<TypeBoxProvider>().get(
|
|
276
|
+
'/',
|
|
277
|
+
{
|
|
278
|
+
schema: {
|
|
279
|
+
response: {
|
|
280
|
+
200: Type.String(),
|
|
281
|
+
400: Type.Number(),
|
|
282
|
+
500: Type.Object({
|
|
283
|
+
error: Type.String()
|
|
284
|
+
})
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
async (_, res) => {
|
|
289
|
+
return false
|
|
290
|
+
}
|
|
291
|
+
))
|
|
292
|
+
|
|
293
|
+
// -------------------------------------------------------------------
|
|
294
|
+
// JsonSchemaToTs Reply Type
|
|
295
|
+
// -------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
|
|
298
|
+
'/',
|
|
299
|
+
{
|
|
300
|
+
schema: {
|
|
301
|
+
response: {
|
|
302
|
+
200: { type: 'string' },
|
|
303
|
+
400: { type: 'number' },
|
|
304
|
+
500: { type: 'object', properties: { error: { type: 'string' } } }
|
|
305
|
+
} as const
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
(_, res) => {
|
|
309
|
+
res.send('hello')
|
|
310
|
+
res.send(42)
|
|
311
|
+
res.send({ error: 'error' })
|
|
312
|
+
}
|
|
313
|
+
))
|
|
314
|
+
|
|
315
|
+
// -------------------------------------------------------------------
|
|
316
|
+
// JsonSchemaToTs Reply Type: Non Assignable
|
|
317
|
+
// -------------------------------------------------------------------
|
|
318
|
+
|
|
319
|
+
expectError(server.withTypeProvider<JsonSchemaToTsProvider>().get(
|
|
320
|
+
'/',
|
|
321
|
+
{
|
|
322
|
+
schema: {
|
|
323
|
+
response: {
|
|
324
|
+
200: { type: 'string' },
|
|
325
|
+
400: { type: 'number' },
|
|
326
|
+
500: { type: 'object', properties: { error: { type: 'string' } } }
|
|
327
|
+
} as const
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
async (_, res) => {
|
|
331
|
+
res.send(false)
|
|
332
|
+
}
|
|
333
|
+
))
|
|
334
|
+
|
|
335
|
+
// -------------------------------------------------------------------
|
|
336
|
+
// JsonSchemaToTs Reply Type Return
|
|
337
|
+
// -------------------------------------------------------------------
|
|
338
|
+
|
|
339
|
+
expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
|
|
340
|
+
'/',
|
|
341
|
+
{
|
|
342
|
+
schema: {
|
|
343
|
+
response: {
|
|
344
|
+
200: { type: 'string' },
|
|
345
|
+
400: { type: 'number' },
|
|
346
|
+
500: { type: 'object', properties: { error: { type: 'string' } } }
|
|
347
|
+
} as const
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
async (_, res) => {
|
|
351
|
+
const option = 1 as 1 | 2 | 3
|
|
352
|
+
switch (option) {
|
|
353
|
+
case 1: return 'hello'
|
|
354
|
+
case 2: return 42
|
|
355
|
+
case 3: return { error: 'error' }
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
))
|
|
359
|
+
// -------------------------------------------------------------------
|
|
360
|
+
// JsonSchemaToTs Reply Type Return: Non Assignable
|
|
361
|
+
// -------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
expectError(server.withTypeProvider<JsonSchemaToTsProvider>().get(
|
|
364
|
+
'/',
|
|
365
|
+
{
|
|
366
|
+
schema: {
|
|
367
|
+
response: {
|
|
368
|
+
200: { type: 'string' },
|
|
369
|
+
400: { type: 'number' },
|
|
370
|
+
500: { type: 'object', properties: { error: { type: 'string' } } }
|
|
371
|
+
} as const
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
async (_, res) => {
|
|
375
|
+
return false
|
|
376
|
+
}
|
|
377
|
+
))
|
|
378
|
+
|
|
379
|
+
// -------------------------------------------------------------------
|
|
380
|
+
// Reply Type Override
|
|
381
|
+
// -------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get<{Reply: boolean}>(
|
|
384
|
+
'/',
|
|
385
|
+
{
|
|
386
|
+
schema: {
|
|
387
|
+
response: {
|
|
388
|
+
200: { type: 'string' },
|
|
389
|
+
400: { type: 'number' },
|
|
390
|
+
500: { type: 'object', properties: { error: { type: 'string' } } }
|
|
391
|
+
} as const
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
async (_, res) => {
|
|
395
|
+
res.send(true)
|
|
396
|
+
}
|
|
397
|
+
))
|
|
398
|
+
|
|
399
|
+
// -------------------------------------------------------------------
|
|
400
|
+
// Reply Type Return Override
|
|
401
|
+
// -------------------------------------------------------------------
|
|
402
|
+
|
|
403
|
+
expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get<{Reply: boolean}>(
|
|
404
|
+
'/',
|
|
405
|
+
{
|
|
406
|
+
schema: {
|
|
407
|
+
response: {
|
|
408
|
+
200: { type: 'string' },
|
|
409
|
+
400: { type: 'number' },
|
|
410
|
+
500: { type: 'object', properties: { error: { type: 'string' } } }
|
|
411
|
+
} as const
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
async (_, res) => {
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
417
|
+
))
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const { test } = require('tap')
|
|
4
|
-
const Joi = require('
|
|
4
|
+
const Joi = require('joi')
|
|
5
5
|
const Fastify = require('..')
|
|
6
6
|
|
|
7
7
|
const schema = {
|
|
@@ -58,7 +58,7 @@ test('should fail immediately with invalid payload', t => {
|
|
|
58
58
|
t.same(res.json(), {
|
|
59
59
|
statusCode: 400,
|
|
60
60
|
error: 'Bad Request',
|
|
61
|
-
message: "body
|
|
61
|
+
message: "body must have required property 'name'"
|
|
62
62
|
})
|
|
63
63
|
t.equal(res.statusCode, 400)
|
|
64
64
|
})
|
|
@@ -140,7 +140,7 @@ test('error inside custom error handler should have validationContext if specifi
|
|
|
140
140
|
return function (data) {
|
|
141
141
|
const error = new Error('this failed')
|
|
142
142
|
error.validationContext = 'customContext'
|
|
143
|
-
return { error
|
|
143
|
+
return { error }
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
}, function (req, reply) {
|
|
@@ -183,10 +183,10 @@ test('should be able to attach validation to request', t => {
|
|
|
183
183
|
|
|
184
184
|
t.same(res.json(), [{
|
|
185
185
|
keyword: 'required',
|
|
186
|
-
|
|
186
|
+
instancePath: '',
|
|
187
187
|
schemaPath: '#/required',
|
|
188
188
|
params: { missingProperty: 'name' },
|
|
189
|
-
message: '
|
|
189
|
+
message: 'must have required property \'name\''
|
|
190
190
|
}])
|
|
191
191
|
t.equal(res.statusCode, 400)
|
|
192
192
|
})
|
|
@@ -213,7 +213,7 @@ test('should respect when attachValidation is explicitly set to false', t => {
|
|
|
213
213
|
t.same(JSON.parse(res.payload), {
|
|
214
214
|
statusCode: 400,
|
|
215
215
|
error: 'Bad Request',
|
|
216
|
-
message: "body
|
|
216
|
+
message: "body must have required property 'name'"
|
|
217
217
|
})
|
|
218
218
|
t.equal(res.statusCode, 400)
|
|
219
219
|
})
|
|
@@ -243,7 +243,7 @@ test('Attached validation error should take precedence over setErrorHandler', t
|
|
|
243
243
|
url: '/'
|
|
244
244
|
}, (err, res) => {
|
|
245
245
|
t.error(err)
|
|
246
|
-
t.same(res.payload, "Attached: Error: body
|
|
246
|
+
t.same(res.payload, "Attached: Error: body must have required property 'name'")
|
|
247
247
|
t.equal(res.statusCode, 400)
|
|
248
248
|
})
|
|
249
249
|
})
|
|
@@ -336,7 +336,7 @@ test('should return a defined output message parsing AJV errors', t => {
|
|
|
336
336
|
url: '/'
|
|
337
337
|
}, (err, res) => {
|
|
338
338
|
t.error(err)
|
|
339
|
-
t.equal(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body
|
|
339
|
+
t.equal(res.payload, '{"statusCode":400,"error":"Bad Request","message":"body must have required property \'name\'"}')
|
|
340
340
|
})
|
|
341
341
|
})
|
|
342
342
|
|
|
@@ -467,7 +467,7 @@ test('should call custom error formatter', t => {
|
|
|
467
467
|
const fastify = Fastify({
|
|
468
468
|
schemaErrorFormatter: (errors, dataVar) => {
|
|
469
469
|
t.equal(errors.length, 1)
|
|
470
|
-
t.equal(errors[0].message, "
|
|
470
|
+
t.equal(errors[0].message, "must have required property 'name'")
|
|
471
471
|
t.equal(dataVar, 'body')
|
|
472
472
|
return new Error('my error')
|
|
473
473
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const { test, before } = require('tap')
|
|
4
|
+
const helper = require('./helper')
|
|
5
5
|
const Fastify = require('..')
|
|
6
6
|
const sget = require('simple-get').concat
|
|
7
7
|
const http = require('http')
|
|
@@ -9,6 +9,13 @@ const split = require('split2')
|
|
|
9
9
|
const append = require('vary').append
|
|
10
10
|
const proxyquire = require('proxyquire')
|
|
11
11
|
|
|
12
|
+
process.removeAllListeners('warning')
|
|
13
|
+
|
|
14
|
+
let localhost
|
|
15
|
+
before(async function () {
|
|
16
|
+
[localhost] = await helper.getLoopbackHost()
|
|
17
|
+
})
|
|
18
|
+
|
|
12
19
|
test('Should register a versioned route', t => {
|
|
13
20
|
t.plan(11)
|
|
14
21
|
const fastify = Fastify()
|
|
@@ -433,7 +440,7 @@ test('test log stream', t => {
|
|
|
433
440
|
const stream = split(JSON.parse)
|
|
434
441
|
const fastify = Fastify({
|
|
435
442
|
logger: {
|
|
436
|
-
stream
|
|
443
|
+
stream,
|
|
437
444
|
level: 'info'
|
|
438
445
|
}
|
|
439
446
|
})
|
|
@@ -442,12 +449,12 @@ test('test log stream', t => {
|
|
|
442
449
|
reply.send(new Error('kaboom'))
|
|
443
450
|
})
|
|
444
451
|
|
|
445
|
-
fastify.listen(0, err => {
|
|
452
|
+
fastify.listen(0, localhost, err => {
|
|
446
453
|
t.error(err)
|
|
447
454
|
fastify.server.unref()
|
|
448
455
|
|
|
449
456
|
http.get({
|
|
450
|
-
hostname:
|
|
457
|
+
hostname: fastify.server.address().hostname,
|
|
451
458
|
port: fastify.server.address().port,
|
|
452
459
|
path: '/',
|
|
453
460
|
method: 'GET',
|
|
@@ -549,7 +556,7 @@ test('Should register a versioned route with custom versioning strategy', t => {
|
|
|
549
556
|
})
|
|
550
557
|
|
|
551
558
|
test('Should get error using an invalid a versioned route, using default validation (deprecated versioning option)', t => {
|
|
552
|
-
t.plan(
|
|
559
|
+
t.plan(3)
|
|
553
560
|
|
|
554
561
|
const fastify = Fastify({
|
|
555
562
|
versioning: {
|
|
@@ -577,15 +584,19 @@ test('Should get error using an invalid a versioned route, using default validat
|
|
|
577
584
|
}
|
|
578
585
|
})
|
|
579
586
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
587
|
+
try {
|
|
588
|
+
fastify.route({
|
|
589
|
+
method: 'GET',
|
|
590
|
+
url: '/',
|
|
591
|
+
// not a string version
|
|
592
|
+
constraints: { version: 2 },
|
|
593
|
+
handler: (req, reply) => {
|
|
594
|
+
reply.send({ hello: 'cant match route v2' })
|
|
595
|
+
}
|
|
596
|
+
})
|
|
597
|
+
} catch (err) {
|
|
598
|
+
t.equal(err.message, 'Version constraint should be a string.')
|
|
599
|
+
}
|
|
589
600
|
|
|
590
601
|
fastify.inject({
|
|
591
602
|
method: 'GET',
|
|
@@ -594,7 +605,8 @@ test('Should get error using an invalid a versioned route, using default validat
|
|
|
594
605
|
Accept: 'application/vnd.example.api+json;version=2'
|
|
595
606
|
}
|
|
596
607
|
}, (err, res) => {
|
|
597
|
-
t.
|
|
608
|
+
t.error(err)
|
|
609
|
+
t.equal(res.statusCode, 404)
|
|
598
610
|
})
|
|
599
611
|
})
|
|
600
612
|
|
|
@@ -663,7 +675,7 @@ test('Should trigger a warning when a versioned route is registered via version
|
|
|
663
675
|
}
|
|
664
676
|
|
|
665
677
|
const route = proxyquire('../lib/route', { './warnings': warning })
|
|
666
|
-
const fastify = proxyquire('..', { './lib/route.js': route })()
|
|
678
|
+
const fastify = proxyquire('..', { './lib/route.js': route })({ exposeHeadRoutes: false })
|
|
667
679
|
|
|
668
680
|
fastify.route({
|
|
669
681
|
method: 'GET',
|
|
@@ -2,21 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
const t = require('tap')
|
|
4
4
|
const test = t.test
|
|
5
|
-
const {
|
|
5
|
+
const { kReplyHijacked } = require('../lib/symbols')
|
|
6
6
|
const wrapThenable = require('../lib/wrapThenable')
|
|
7
|
+
const Reply = require('../lib/reply')
|
|
7
8
|
|
|
8
|
-
test('should resolve immediately when reply[
|
|
9
|
+
test('should resolve immediately when reply[kReplyHijacked] is true', t => {
|
|
9
10
|
const reply = {}
|
|
10
|
-
reply[
|
|
11
|
+
reply[kReplyHijacked] = true
|
|
11
12
|
const thenable = Promise.resolve()
|
|
12
13
|
wrapThenable(thenable, reply)
|
|
13
14
|
t.end()
|
|
14
15
|
})
|
|
15
16
|
|
|
16
|
-
test('should reject immediately when reply[
|
|
17
|
+
test('should reject immediately when reply[kReplyHijacked] is true', t => {
|
|
17
18
|
t.plan(1)
|
|
18
|
-
const reply = {
|
|
19
|
-
reply[
|
|
19
|
+
const reply = new Reply({}, {}, {})
|
|
20
|
+
reply[kReplyHijacked] = true
|
|
20
21
|
reply.log = {
|
|
21
22
|
error: ({ err }) => {
|
|
22
23
|
t.equal(err.message, 'Reply sent already')
|
package/types/.eslintrc.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"files": ["*.test-d.ts"],
|
|
32
32
|
"rules": {
|
|
33
33
|
"no-unused-vars": "off",
|
|
34
|
-
"
|
|
34
|
+
"n/handle-callback-err": "off",
|
|
35
35
|
"@typescript-eslint/no-empty-function": "off",
|
|
36
36
|
"@typescript-eslint/explicit-function-return-type": "off",
|
|
37
37
|
"@typescript-eslint/no-unused-vars": "off",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { RawServerBase, RawServerDefault, RawRequestDefaultExpression } from './utils'
|
|
2
2
|
import { FastifyRequest } from './request'
|
|
3
3
|
import { RouteGenericInterface } from './route'
|
|
4
|
+
import { FastifyTypeProvider, FastifyTypeProviderDefault } from './type-provider'
|
|
5
|
+
import { FastifySchema } from './schema'
|
|
4
6
|
|
|
5
7
|
type ContentTypeParserDoneFunction = (err: Error | null, body?: any) => void
|
|
6
8
|
|
|
@@ -12,8 +14,10 @@ export type FastifyBodyParser<
|
|
|
12
14
|
RawServer extends RawServerBase = RawServerDefault,
|
|
13
15
|
RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
|
|
14
16
|
RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
SchemaCompiler extends FastifySchema = FastifySchema,
|
|
18
|
+
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
|
|
19
|
+
> = ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, rawBody: RawBody, done: ContentTypeParserDoneFunction) => void)
|
|
20
|
+
| ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, rawBody: RawBody) => Promise<any>)
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* Content Type Parser method that operates on request content
|
|
@@ -22,31 +26,36 @@ export type FastifyContentTypeParser<
|
|
|
22
26
|
RawServer extends RawServerBase = RawServerDefault,
|
|
23
27
|
RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
|
|
24
28
|
RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
SchemaCompiler extends FastifySchema = FastifySchema,
|
|
30
|
+
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
|
|
31
|
+
> = ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, payload: RawRequest) => Promise<any>)
|
|
32
|
+
| ((request: FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider>, payload: RawRequest, done: ContentTypeParserDoneFunction) => void)
|
|
27
33
|
|
|
28
34
|
/**
|
|
29
35
|
* Natively, Fastify only supports 'application/json' and 'text/plain' content types. 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.
|
|
30
36
|
*/
|
|
31
37
|
export interface AddContentTypeParser<
|
|
32
38
|
RawServer extends RawServerBase = RawServerDefault,
|
|
33
|
-
RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer
|
|
39
|
+
RawRequest extends RawRequestDefaultExpression<RawServer> = RawRequestDefaultExpression<RawServer>,
|
|
40
|
+
RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
|
|
41
|
+
SchemaCompiler extends FastifySchema = FastifySchema,
|
|
42
|
+
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
|
|
34
43
|
> {
|
|
35
44
|
(
|
|
36
45
|
contentType: string | string[] | RegExp,
|
|
37
46
|
opts: {
|
|
38
47
|
bodyLimit?: number;
|
|
39
48
|
},
|
|
40
|
-
parser: FastifyContentTypeParser<RawServer, RawRequest>
|
|
49
|
+
parser: FastifyContentTypeParser<RawServer, RawRequest, RouteGeneric, SchemaCompiler, TypeProvider>
|
|
41
50
|
): void;
|
|
42
|
-
(contentType: string | string[] | RegExp, parser: FastifyContentTypeParser<RawServer, RawRequest>): void;
|
|
51
|
+
(contentType: string | string[] | RegExp, parser: FastifyContentTypeParser<RawServer, RawRequest, RouteGeneric, SchemaCompiler, TypeProvider>): void;
|
|
43
52
|
<parseAs extends string | Buffer>(
|
|
44
53
|
contentType: string | string[] | RegExp,
|
|
45
54
|
opts: {
|
|
46
55
|
parseAs: parseAs extends Buffer ? 'buffer' : 'string';
|
|
47
56
|
bodyLimit?: number;
|
|
48
57
|
},
|
|
49
|
-
parser: FastifyBodyParser<parseAs, RawServer, RawRequest>
|
|
58
|
+
parser: FastifyBodyParser<parseAs, RawServer, RawRequest, RouteGeneric, SchemaCompiler, TypeProvider>
|
|
50
59
|
): void;
|
|
51
60
|
}
|
|
52
61
|
|