fastify 3.9.2 → 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GOVERNANCE.md +1 -1
- package/README.md +12 -8
- package/SECURITY.md +3 -3
- package/docs/ContentTypeParser.md +1 -1
- package/docs/Ecosystem.md +16 -6
- package/docs/Encapsulation.md +5 -2
- package/docs/Fluent-Schema.md +4 -4
- package/docs/Getting-Started.md +1 -1
- package/docs/Hooks.md +28 -1
- package/docs/Lifecycle.md +8 -1
- package/docs/Middleware.md +5 -4
- package/docs/Reply.md +13 -4
- package/docs/Routes.md +4 -3
- package/docs/Server.md +78 -4
- package/docs/Serverless.md +23 -51
- package/docs/TypeScript.md +35 -18
- package/docs/Validation-and-Serialization.md +4 -4
- package/docs/Write-Plugin.md +4 -4
- package/examples/hooks-benchmark.js +12 -12
- package/examples/hooks.js +16 -16
- package/examples/plugin.js +2 -2
- package/examples/route-prefix.js +4 -4
- package/fastify.d.ts +16 -1
- package/fastify.js +33 -16
- package/isolate-0x426d1e0-1227-v8.log +4019 -0
- package/isolate-0x4d4c7e0-1988-v8.log +4081 -0
- package/lib/errors.js +6 -0
- package/lib/headRoute.js +31 -0
- package/lib/pluginOverride.js +5 -5
- package/lib/pluginUtils.js +7 -6
- package/lib/reply.js +14 -2
- package/lib/reqIdGenFactory.js +5 -0
- package/lib/request.js +1 -1
- package/lib/route.js +66 -41
- package/lib/schema-compilers.js +5 -3
- package/lib/schema-controller.js +106 -0
- package/lib/schemas.js +14 -24
- package/lib/server.js +1 -0
- package/lib/symbols.js +1 -3
- package/lib/warnings.js +2 -0
- package/lib/wrapThenable.js +2 -1
- package/package.json +25 -21
- package/test/404s.test.js +120 -120
- package/test/500s.test.js +8 -8
- package/test/async-await.test.js +29 -1
- package/test/close.test.js +8 -8
- package/test/context-config.test.js +52 -0
- package/test/custom-parser.test.js +8 -8
- package/test/decorator.test.js +49 -49
- package/test/default-route.test.js +43 -0
- package/test/fastify-instance.test.js +2 -2
- package/test/fluent-schema.test.js +3 -3
- package/test/handler-context.test.js +2 -2
- package/test/hooks-async.test.js +3 -3
- package/test/hooks.on-ready.test.js +12 -12
- package/test/hooks.test.js +75 -32
- package/test/http2/closing.test.js +23 -1
- package/test/inject.test.js +6 -6
- package/test/input-validation.js +2 -2
- package/test/internals/hookRunner.test.js +50 -50
- package/test/internals/reply.test.js +47 -22
- package/test/internals/request.test.js +3 -9
- package/test/internals/version.test.js +2 -2
- package/test/logger.test.js +30 -30
- package/test/middleware.test.js +4 -4
- package/test/plugin.helper.js +2 -2
- package/test/plugin.test.js +154 -99
- package/test/register.test.js +11 -11
- package/test/request-error.test.js +2 -2
- package/test/route-hooks.test.js +24 -24
- package/test/route-prefix.test.js +81 -52
- package/test/route.test.js +568 -0
- package/test/schema-feature.test.js +168 -38
- package/test/schema-serialization.test.js +4 -4
- package/test/schema-special-usage.test.js +136 -0
- package/test/schema-validation.test.js +7 -7
- package/test/skip-reply-send.test.js +315 -0
- package/test/stream.test.js +6 -6
- package/test/throw.test.js +4 -4
- package/test/types/instance.test-d.ts +5 -3
- package/test/types/plugin.test-d.ts +7 -7
- package/test/types/reply.test-d.ts +1 -0
- package/test/types/schema.test-d.ts +15 -0
- package/test/validation-error-handling.test.js +5 -5
- package/test/versioned-routes.test.js +1 -1
- package/types/content-type-parser.d.ts +1 -1
- package/types/instance.d.ts +6 -3
- package/types/plugin.d.ts +1 -1
- package/types/reply.d.ts +1 -0
- package/types/route.d.ts +8 -2
- package/types/schema.d.ts +3 -0
- package/test/skip-reply-send.js +0 -98
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const split = require('split2')
|
|
5
|
+
const net = require('net')
|
|
6
|
+
const Fastify = require('../fastify')
|
|
7
|
+
|
|
8
|
+
process.removeAllListeners('warning')
|
|
9
|
+
|
|
10
|
+
const lifecycleHooks = [
|
|
11
|
+
'onRequest',
|
|
12
|
+
'preParsing',
|
|
13
|
+
'preValidation',
|
|
14
|
+
'preHandler',
|
|
15
|
+
'preSerialization',
|
|
16
|
+
'onSend',
|
|
17
|
+
'onTimeout',
|
|
18
|
+
'onResponse',
|
|
19
|
+
'onError'
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
test('skip automatic reply.send() with reply.sent = true and a body', (t) => {
|
|
23
|
+
const stream = split(JSON.parse)
|
|
24
|
+
const app = Fastify({
|
|
25
|
+
logger: {
|
|
26
|
+
stream: stream
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
stream.on('data', (line) => {
|
|
31
|
+
t.notEqual(line.level, 40) // there are no errors
|
|
32
|
+
t.notEqual(line.level, 50) // there are no errors
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
app.get('/', (req, reply) => {
|
|
36
|
+
reply.sent = true
|
|
37
|
+
reply.raw.end('hello world')
|
|
38
|
+
|
|
39
|
+
return Promise.resolve('this will be skipped')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return app.inject({
|
|
43
|
+
method: 'GET',
|
|
44
|
+
url: '/'
|
|
45
|
+
}).then((res) => {
|
|
46
|
+
t.equal(res.statusCode, 200)
|
|
47
|
+
t.equal(res.body, 'hello world')
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('skip automatic reply.send() with reply.sent = true and no body', (t) => {
|
|
52
|
+
const stream = split(JSON.parse)
|
|
53
|
+
const app = Fastify({
|
|
54
|
+
logger: {
|
|
55
|
+
stream: stream
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
stream.on('data', (line) => {
|
|
60
|
+
t.notEqual(line.level, 40) // there are no error
|
|
61
|
+
t.notEqual(line.level, 50) // there are no error
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
app.get('/', (req, reply) => {
|
|
65
|
+
reply.sent = true
|
|
66
|
+
reply.raw.end('hello world')
|
|
67
|
+
|
|
68
|
+
return Promise.resolve()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
return app.inject({
|
|
72
|
+
method: 'GET',
|
|
73
|
+
url: '/'
|
|
74
|
+
}).then((res) => {
|
|
75
|
+
t.equal(res.statusCode, 200)
|
|
76
|
+
t.equal(res.body, 'hello world')
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('skip automatic reply.send() with reply.sent = true and an error', (t) => {
|
|
81
|
+
const stream = split(JSON.parse)
|
|
82
|
+
const app = Fastify({
|
|
83
|
+
logger: {
|
|
84
|
+
stream: stream
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
let errorSeen = false
|
|
89
|
+
|
|
90
|
+
stream.on('data', (line) => {
|
|
91
|
+
if (line.level === 50) {
|
|
92
|
+
errorSeen = true
|
|
93
|
+
t.equal(line.err.message, 'kaboom')
|
|
94
|
+
t.equal(line.msg, 'Promise errored, but reply.sent = true was set')
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
app.get('/', (req, reply) => {
|
|
99
|
+
reply.sent = true
|
|
100
|
+
reply.raw.end('hello world')
|
|
101
|
+
|
|
102
|
+
return Promise.reject(new Error('kaboom'))
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
return app.inject({
|
|
106
|
+
method: 'GET',
|
|
107
|
+
url: '/'
|
|
108
|
+
}).then((res) => {
|
|
109
|
+
t.equal(errorSeen, true)
|
|
110
|
+
t.equal(res.statusCode, 200)
|
|
111
|
+
t.equal(res.body, 'hello world')
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
function testHandlerOrBeforeHandlerHook (test, hookOrHandler) {
|
|
116
|
+
const idx = hookOrHandler === 'handler' ? lifecycleHooks.indexOf('preHandler') : lifecycleHooks.indexOf(hookOrHandler)
|
|
117
|
+
const previousHooks = lifecycleHooks.slice(0, idx)
|
|
118
|
+
const nextHooks = lifecycleHooks.slice(idx + 1)
|
|
119
|
+
|
|
120
|
+
test(`Hijacking inside ${hookOrHandler} skips all the following hooks and handler execution`, t => {
|
|
121
|
+
t.plan(4)
|
|
122
|
+
const test = t.test
|
|
123
|
+
|
|
124
|
+
test('Sending a response using reply.raw => onResponse hook is called', t => {
|
|
125
|
+
const stream = split(JSON.parse)
|
|
126
|
+
const app = Fastify({
|
|
127
|
+
logger: {
|
|
128
|
+
stream: stream
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
stream.on('data', (line) => {
|
|
133
|
+
t.notEqual(line.level, 40) // there are no errors
|
|
134
|
+
t.notEqual(line.level, 50) // there are no errors
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
|
|
138
|
+
|
|
139
|
+
if (hookOrHandler === 'handler') {
|
|
140
|
+
app.get('/', (req, reply) => {
|
|
141
|
+
reply.hijack()
|
|
142
|
+
reply.raw.end(`hello from ${hookOrHandler}`)
|
|
143
|
+
})
|
|
144
|
+
} else {
|
|
145
|
+
app.addHook(hookOrHandler, async (req, reply) => {
|
|
146
|
+
reply.hijack()
|
|
147
|
+
reply.raw.end(`hello from ${hookOrHandler}`)
|
|
148
|
+
})
|
|
149
|
+
app.get('/', (req, reply) => t.fail('Handler should not be called'))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
nextHooks.forEach(h => {
|
|
153
|
+
if (h === 'onResponse') {
|
|
154
|
+
app.addHook(h, async (req, reply) => t.pass(`${h} should be called`))
|
|
155
|
+
} else {
|
|
156
|
+
app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`))
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
return app.inject({
|
|
161
|
+
method: 'GET',
|
|
162
|
+
url: '/'
|
|
163
|
+
}).then((res) => {
|
|
164
|
+
t.equal(res.statusCode, 200)
|
|
165
|
+
t.equal(res.body, `hello from ${hookOrHandler}`)
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
test('Sending a response using req.socket => onResponse not called', t => {
|
|
170
|
+
const stream = split(JSON.parse)
|
|
171
|
+
const app = Fastify({
|
|
172
|
+
logger: {
|
|
173
|
+
stream: stream
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
t.tearDown(() => app.close())
|
|
177
|
+
|
|
178
|
+
stream.on('data', (line) => {
|
|
179
|
+
t.notEqual(line.level, 40) // there are no errors
|
|
180
|
+
t.notEqual(line.level, 50) // there are no errors
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
|
|
184
|
+
|
|
185
|
+
if (hookOrHandler === 'handler') {
|
|
186
|
+
app.get('/', (req, reply) => {
|
|
187
|
+
reply.hijack()
|
|
188
|
+
req.socket.write('HTTP/1.1 200 OK\r\n\r\n')
|
|
189
|
+
req.socket.write(`hello from ${hookOrHandler}`)
|
|
190
|
+
req.socket.end()
|
|
191
|
+
})
|
|
192
|
+
} else {
|
|
193
|
+
app.addHook(hookOrHandler, async (req, reply) => {
|
|
194
|
+
reply.hijack()
|
|
195
|
+
req.socket.write('HTTP/1.1 200 OK\r\n\r\n')
|
|
196
|
+
req.socket.write(`hello from ${hookOrHandler}`)
|
|
197
|
+
req.socket.end()
|
|
198
|
+
})
|
|
199
|
+
app.get('/', (req, reply) => t.fail('Handler should not be called'))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
|
|
203
|
+
|
|
204
|
+
app.listen(0, err => {
|
|
205
|
+
t.error(err)
|
|
206
|
+
const client = net.createConnection({ port: (app.server.address()).port }, () => {
|
|
207
|
+
client.write('GET / HTTP/1.1\r\n\r\n')
|
|
208
|
+
client.once('data', data => {
|
|
209
|
+
t.match(data.toString(), new RegExp(`hello from ${hookOrHandler}`, 'i'))
|
|
210
|
+
client.end(() => t.end())
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('Throwing an error doesnt trigger any hooks', t => {
|
|
217
|
+
const stream = split(JSON.parse)
|
|
218
|
+
const app = Fastify({
|
|
219
|
+
logger: {
|
|
220
|
+
stream: stream
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
t.tearDown(() => app.close())
|
|
224
|
+
|
|
225
|
+
let errorSeen = false
|
|
226
|
+
stream.on('data', (line) => {
|
|
227
|
+
if (hookOrHandler === 'handler') {
|
|
228
|
+
if (line.level === 40) {
|
|
229
|
+
errorSeen = true
|
|
230
|
+
t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT')
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
t.notEqual(line.level, 40) // there are no errors
|
|
234
|
+
t.notEqual(line.level, 50) // there are no errors
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
|
|
239
|
+
|
|
240
|
+
if (hookOrHandler === 'handler') {
|
|
241
|
+
app.get('/', (req, reply) => {
|
|
242
|
+
reply.hijack()
|
|
243
|
+
throw new Error('This wil be skipped')
|
|
244
|
+
})
|
|
245
|
+
} else {
|
|
246
|
+
app.addHook(hookOrHandler, async (req, reply) => {
|
|
247
|
+
reply.hijack()
|
|
248
|
+
throw new Error('This wil be skipped')
|
|
249
|
+
})
|
|
250
|
+
app.get('/', (req, reply) => t.fail('Handler should not be called'))
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
|
|
254
|
+
|
|
255
|
+
return Promise.race([
|
|
256
|
+
app.inject({ method: 'GET', url: '/' }),
|
|
257
|
+
new Promise((resolve, reject) => setTimeout(resolve, 1000))
|
|
258
|
+
]).then((err, res) => {
|
|
259
|
+
t.error(err)
|
|
260
|
+
if (hookOrHandler === 'handler') {
|
|
261
|
+
t.equal(errorSeen, true)
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
test('Calling reply.send() after hijacking logs a warning', t => {
|
|
267
|
+
const stream = split(JSON.parse)
|
|
268
|
+
const app = Fastify({
|
|
269
|
+
logger: {
|
|
270
|
+
stream: stream
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
let errorSeen = false
|
|
275
|
+
|
|
276
|
+
stream.on('data', (line) => {
|
|
277
|
+
if (line.level === 40) {
|
|
278
|
+
errorSeen = true
|
|
279
|
+
t.equal(line.err.code, 'FST_ERR_REP_ALREADY_SENT')
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.pass(`${h} should be called`)))
|
|
284
|
+
|
|
285
|
+
if (hookOrHandler === 'handler') {
|
|
286
|
+
app.get('/', (req, reply) => {
|
|
287
|
+
reply.hijack()
|
|
288
|
+
reply.send('hello from reply.send()')
|
|
289
|
+
})
|
|
290
|
+
} else {
|
|
291
|
+
app.addHook(hookOrHandler, async (req, reply) => {
|
|
292
|
+
reply.hijack()
|
|
293
|
+
reply.send('hello from reply.send()')
|
|
294
|
+
})
|
|
295
|
+
app.get('/', (req, reply) => t.fail('Handler should not be called'))
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.fail(`${h} should not be called`)))
|
|
299
|
+
|
|
300
|
+
return Promise.race([
|
|
301
|
+
app.inject({ method: 'GET', url: '/' }),
|
|
302
|
+
new Promise((resolve, reject) => setTimeout(resolve, 1000))
|
|
303
|
+
]).then((err, res) => {
|
|
304
|
+
t.error(err)
|
|
305
|
+
t.equal(errorSeen, true)
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
testHandlerOrBeforeHandlerHook(test, 'onRequest')
|
|
312
|
+
testHandlerOrBeforeHandlerHook(test, 'preParsing')
|
|
313
|
+
testHandlerOrBeforeHandlerHook(test, 'preValidation')
|
|
314
|
+
testHandlerOrBeforeHandlerHook(test, 'preHandler')
|
|
315
|
+
testHandlerOrBeforeHandlerHook(test, 'handler')
|
package/test/stream.test.js
CHANGED
|
@@ -58,10 +58,10 @@ test('should trigger the onSend hook', t => {
|
|
|
58
58
|
reply.send(fs.createReadStream(__filename, 'utf8'))
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
-
fastify.addHook('onSend', (req, reply, payload,
|
|
61
|
+
fastify.addHook('onSend', (req, reply, payload, done) => {
|
|
62
62
|
t.ok(payload._readableState)
|
|
63
63
|
reply.header('Content-Type', 'application/javascript')
|
|
64
|
-
|
|
64
|
+
done()
|
|
65
65
|
})
|
|
66
66
|
|
|
67
67
|
fastify.inject({
|
|
@@ -83,7 +83,7 @@ test('should trigger the onSend hook only twice if pumping the stream fails, fir
|
|
|
83
83
|
})
|
|
84
84
|
|
|
85
85
|
let counter = 0
|
|
86
|
-
fastify.addHook('onSend', (req, reply, payload,
|
|
86
|
+
fastify.addHook('onSend', (req, reply, payload, done) => {
|
|
87
87
|
if (counter === 0) {
|
|
88
88
|
t.ok(payload._readableState)
|
|
89
89
|
} else if (counter === 1) {
|
|
@@ -91,7 +91,7 @@ test('should trigger the onSend hook only twice if pumping the stream fails, fir
|
|
|
91
91
|
t.strictEqual(error.statusCode, 500)
|
|
92
92
|
}
|
|
93
93
|
counter++
|
|
94
|
-
|
|
94
|
+
done()
|
|
95
95
|
})
|
|
96
96
|
|
|
97
97
|
fastify.listen(0, err => {
|
|
@@ -114,7 +114,7 @@ test('onSend hook stream', t => {
|
|
|
114
114
|
reply.send({ hello: 'world' })
|
|
115
115
|
})
|
|
116
116
|
|
|
117
|
-
fastify.addHook('onSend', (req, reply, payload,
|
|
117
|
+
fastify.addHook('onSend', (req, reply, payload, done) => {
|
|
118
118
|
const gzStream = zlib.createGzip()
|
|
119
119
|
|
|
120
120
|
reply.header('Content-Encoding', 'gzip')
|
|
@@ -123,7 +123,7 @@ test('onSend hook stream', t => {
|
|
|
123
123
|
gzStream,
|
|
124
124
|
t.error
|
|
125
125
|
)
|
|
126
|
-
|
|
126
|
+
done(null, gzStream)
|
|
127
127
|
})
|
|
128
128
|
|
|
129
129
|
fastify.inject({
|
package/test/throw.test.js
CHANGED
|
@@ -60,9 +60,9 @@ test('Fastify should throw for an invalid schema, printing the error route - bod
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const fastify = Fastify()
|
|
63
|
-
fastify.register((instance, opts,
|
|
63
|
+
fastify.register((instance, opts, done) => {
|
|
64
64
|
instance.post('/form', { schema: { body: badSchema } }, () => {})
|
|
65
|
-
|
|
65
|
+
done()
|
|
66
66
|
}, { prefix: 'hello' })
|
|
67
67
|
|
|
68
68
|
fastify.ready(err => {
|
|
@@ -155,11 +155,11 @@ test('Should not throw on duplicate decorator encapsulation', t => {
|
|
|
155
155
|
|
|
156
156
|
fastify.decorate('foo2', foo2Obj)
|
|
157
157
|
|
|
158
|
-
fastify.register(function (fastify, opts,
|
|
158
|
+
fastify.register(function (fastify, opts, done) {
|
|
159
159
|
t.notThrow(() => {
|
|
160
160
|
fastify.decorate('foo2', foo2Obj)
|
|
161
161
|
})
|
|
162
|
-
|
|
162
|
+
done()
|
|
163
163
|
})
|
|
164
164
|
|
|
165
165
|
fastify.ready()
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import fastify, { FastifyError, FastifyInstance } from '../../fastify'
|
|
1
|
+
import fastify, { FastifyError, FastifyInstance, ValidationResult } from '../../fastify'
|
|
2
2
|
import { expectAssignable, expectError, expectType } from 'tsd'
|
|
3
3
|
|
|
4
4
|
const server = fastify()
|
|
@@ -28,6 +28,8 @@ expectAssignable<FastifyInstance>(
|
|
|
28
28
|
})
|
|
29
29
|
)
|
|
30
30
|
|
|
31
|
+
expectType<ValidationResult[] | undefined>(FastifyError().validation)
|
|
32
|
+
|
|
31
33
|
function fastifyErrorHandler (this: FastifyInstance, error: FastifyError) {}
|
|
32
34
|
server.setErrorHandler(fastifyErrorHandler)
|
|
33
35
|
|
|
@@ -43,8 +45,8 @@ server.setReplySerializer(function (payload, statusCode) {
|
|
|
43
45
|
return 'serialized'
|
|
44
46
|
})
|
|
45
47
|
|
|
46
|
-
function
|
|
47
|
-
expectError(server.setReplySerializer(
|
|
48
|
+
function invalidReplySerializer (payload: number, statusCode: string) {}
|
|
49
|
+
expectError(server.setReplySerializer(invalidReplySerializer))
|
|
48
50
|
|
|
49
51
|
function serializerWithInvalidReturn (payload: unknown, statusCode: number) {}
|
|
50
52
|
expectError(server.setReplySerializer(serializerWithInvalidReturn))
|
|
@@ -10,10 +10,10 @@ interface TestOptions extends FastifyPluginOptions {
|
|
|
10
10
|
option1: string;
|
|
11
11
|
option2: boolean;
|
|
12
12
|
}
|
|
13
|
-
const testPluginOpts: FastifyPluginCallback<TestOptions> = function (instance, opts,
|
|
13
|
+
const testPluginOpts: FastifyPluginCallback<TestOptions> = function (instance, opts, done) { }
|
|
14
14
|
const testPluginOptsAsync: FastifyPluginAsync<TestOptions> = async function (instance, opts) { }
|
|
15
15
|
|
|
16
|
-
const testPluginOptsWithType = (instance: FastifyInstance, opts: FastifyPluginOptions,
|
|
16
|
+
const testPluginOptsWithType = (instance: FastifyInstance, opts: FastifyPluginOptions, done: (error?: FastifyError) => void) => { }
|
|
17
17
|
const testPluginOptsWithTypeAsync = async (instance: FastifyInstance, opts: FastifyPluginOptions) => { }
|
|
18
18
|
|
|
19
19
|
expectError(fastify().register(testPluginOpts, {})) // error because missing required options from generic declaration
|
|
@@ -22,14 +22,14 @@ expectError(fastify().register(testPluginOptsAsync, {})) // error because missin
|
|
|
22
22
|
expectAssignable<FastifyInstance>(fastify().register(testPluginOpts, { option1: '', option2: true }))
|
|
23
23
|
expectAssignable<FastifyInstance>(fastify().register(testPluginOptsAsync, { option1: '', option2: true }))
|
|
24
24
|
|
|
25
|
-
expectAssignable<FastifyInstance>(fastify().register(function (instance, opts,
|
|
26
|
-
expectAssignable<FastifyInstance>(fastify().register(function (instance, opts,
|
|
27
|
-
expectAssignable<FastifyInstance>(fastify().register(function (instance, opts,
|
|
25
|
+
expectAssignable<FastifyInstance>(fastify().register(function (instance, opts, done) { }))
|
|
26
|
+
expectAssignable<FastifyInstance>(fastify().register(function (instance, opts, done) { }, () => { }))
|
|
27
|
+
expectAssignable<FastifyInstance>(fastify().register(function (instance, opts, done) { }, { logLevel: 'info', prefix: 'foobar' }))
|
|
28
28
|
|
|
29
29
|
expectAssignable<FastifyInstance>(fastify().register(import('./dummy-plugin')))
|
|
30
30
|
expectAssignable<FastifyInstance>(fastify().register(import('./dummy-plugin'), { foo: 1 }))
|
|
31
31
|
|
|
32
|
-
const testPluginCallback: FastifyPluginCallback = function (instance, opts,
|
|
32
|
+
const testPluginCallback: FastifyPluginCallback = function (instance, opts, done) { }
|
|
33
33
|
expectAssignable<FastifyInstance>(fastify().register(testPluginCallback, {}))
|
|
34
34
|
|
|
35
35
|
const testPluginAsync: FastifyPluginAsync = async function (instance, opts) { }
|
|
@@ -39,7 +39,7 @@ expectAssignable<FastifyInstance>(fastify().register(function (instance, opts):
|
|
|
39
39
|
expectAssignable<FastifyInstance>(fastify().register(async function (instance, opts) { }, () => { }))
|
|
40
40
|
expectAssignable<FastifyInstance>(fastify().register(async function (instance, opts) { }, { logLevel: 'info', prefix: 'foobar' }))
|
|
41
41
|
|
|
42
|
-
expectError(fastify().register(function (instance, opts,
|
|
42
|
+
expectError(fastify().register(function (instance, opts, done) { }, { logLevel: '' })) // must use a valid logLevel
|
|
43
43
|
|
|
44
44
|
const httpsServer = fastify({ https: {} })
|
|
45
45
|
expectType<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse>>>(httpsServer)
|
|
@@ -21,6 +21,7 @@ const getHandler: RouteHandlerMethod = function (_request, reply) {
|
|
|
21
21
|
expectType<(key: string) => void>(reply.removeHeader)
|
|
22
22
|
expectType<(key: string) => boolean>(reply.hasHeader)
|
|
23
23
|
expectType<{(statusCode: number, url: string): FastifyReply; (url: string): FastifyReply }>(reply.redirect)
|
|
24
|
+
expectType<() => FastifyReply>(reply.hijack)
|
|
24
25
|
expectType<() => void>(reply.callNotFound)
|
|
25
26
|
expectType<() => number>(reply.getResponseTime)
|
|
26
27
|
expectType<(contentType: string) => FastifyReply>(reply.type)
|
|
@@ -36,6 +36,21 @@ expectAssignable<FastifyInstance>(server.setValidatorCompiler(({ schema }) => {
|
|
|
36
36
|
return new Ajv().compile(schema)
|
|
37
37
|
}))
|
|
38
38
|
|
|
39
|
+
expectAssignable<FastifyInstance>(server.setSerializerCompiler(() => {
|
|
40
|
+
return data => JSON.stringify(data)
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
expectAssignable<FastifyInstance>(server.post('/test', {
|
|
44
|
+
validatorCompiler: ({ schema }) => {
|
|
45
|
+
return data => {
|
|
46
|
+
if (!data || data.constructor !== Object) {
|
|
47
|
+
return { error: new Error('value is not an object') }
|
|
48
|
+
}
|
|
49
|
+
return { value: data }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}, async req => req.body))
|
|
53
|
+
|
|
39
54
|
expectError(server.get(
|
|
40
55
|
'/unknown-schema-prop',
|
|
41
56
|
{
|
|
@@ -219,7 +219,7 @@ test('should respect when attachValidation is explicitly set to false', t => {
|
|
|
219
219
|
})
|
|
220
220
|
})
|
|
221
221
|
|
|
222
|
-
test('Attached validation error should take
|
|
222
|
+
test('Attached validation error should take precedence over setErrorHandler', t => {
|
|
223
223
|
t.plan(3)
|
|
224
224
|
|
|
225
225
|
const fastify = Fastify()
|
|
@@ -692,7 +692,7 @@ test('plugin override', t => {
|
|
|
692
692
|
}
|
|
693
693
|
})
|
|
694
694
|
|
|
695
|
-
fastify.register((instance, opts,
|
|
695
|
+
fastify.register((instance, opts, done) => {
|
|
696
696
|
instance.setSchemaErrorFormatter((errors, dataVar) => {
|
|
697
697
|
return new Error('C')
|
|
698
698
|
})
|
|
@@ -708,12 +708,12 @@ test('plugin override', t => {
|
|
|
708
708
|
|
|
709
709
|
instance.post('/c', { schema }, echoBody)
|
|
710
710
|
|
|
711
|
-
instance.register((subinstance, opts,
|
|
711
|
+
instance.register((subinstance, opts, done) => {
|
|
712
712
|
subinstance.post('/stillC', { schema }, echoBody)
|
|
713
|
-
|
|
713
|
+
done()
|
|
714
714
|
})
|
|
715
715
|
|
|
716
|
-
|
|
716
|
+
done()
|
|
717
717
|
})
|
|
718
718
|
|
|
719
719
|
fastify.post('/b', { schema }, echoBody)
|
|
@@ -404,7 +404,7 @@ test('test log stream', t => {
|
|
|
404
404
|
})
|
|
405
405
|
})
|
|
406
406
|
|
|
407
|
-
test('Should register a versioned route with
|
|
407
|
+
test('Should register a versioned route with custom versioning strategy', t => {
|
|
408
408
|
t.plan(8)
|
|
409
409
|
|
|
410
410
|
const versioning = {
|
|
@@ -5,7 +5,7 @@ import { RouteGenericInterface } from './route'
|
|
|
5
5
|
type ContentTypeParserDoneFunction = (err: Error | null, body?: any) => void
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Body parser method that
|
|
8
|
+
* Body parser method that operators on request body
|
|
9
9
|
*/
|
|
10
10
|
export type FastifyBodyParser<
|
|
11
11
|
RawBody extends string | Buffer,
|
package/types/instance.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Chain as LightMyRequestChain, InjectOptions, Response as LightMyRequestResponse, CallbackFunc as LightMyRequestCallback } from 'light-my-request'
|
|
2
|
-
import { RouteOptions, RouteShorthandMethod, RouteGenericInterface } from './route'
|
|
3
|
-
import { FastifySchemaCompiler, FastifySchemaValidationError } from './schema'
|
|
2
|
+
import { RouteOptions, RouteShorthandMethod, RouteGenericInterface, DefaultRoute } from './route'
|
|
3
|
+
import { FastifySchemaCompiler, FastifySchemaValidationError, FastifySerializerCompiler } from './schema'
|
|
4
4
|
import { RawServerBase, RawRequestDefaultExpression, RawServerDefault, RawReplyDefaultExpression, ContextConfigDefault } from './utils'
|
|
5
5
|
import { FastifyLoggerInstance } from './logger'
|
|
6
6
|
import { FastifyRegister } from './register'
|
|
@@ -59,6 +59,9 @@ export interface FastifyInstance<
|
|
|
59
59
|
|
|
60
60
|
register: FastifyRegister<FastifyInstance<RawServer, RawRequest, RawReply, Logger> & PromiseLike<undefined>>;
|
|
61
61
|
|
|
62
|
+
getDefaultRoute: DefaultRoute<RawRequest, RawReply>;
|
|
63
|
+
setDefaultRoute(defaultRoute: DefaultRoute<RawRequest, RawReply>): void;
|
|
64
|
+
|
|
62
65
|
route<
|
|
63
66
|
RouteGeneric extends RouteGenericInterface = RouteGenericInterface,
|
|
64
67
|
ContextConfig = ContextConfigDefault
|
|
@@ -332,7 +335,7 @@ export interface FastifyInstance<
|
|
|
332
335
|
/**
|
|
333
336
|
* Set the schema serializer for all routes.
|
|
334
337
|
*/
|
|
335
|
-
setSerializerCompiler(schemaCompiler:
|
|
338
|
+
setSerializerCompiler(schemaCompiler: FastifySerializerCompiler): FastifyInstance<RawServer, RawRequest, RawReply, Logger>;
|
|
336
339
|
|
|
337
340
|
/**
|
|
338
341
|
* Set the reply serializer for all routes.
|
package/types/plugin.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type FastifyPluginOptions = Record<string, any>
|
|
|
11
11
|
export type FastifyPluginCallback<Options extends FastifyPluginOptions = Record<never, never>, Server extends RawServerBase = RawServerDefault> = (
|
|
12
12
|
instance: FastifyInstance<Server, RawRequestDefaultExpression<Server>, RawReplyDefaultExpression<Server>>,
|
|
13
13
|
opts: Options,
|
|
14
|
-
|
|
14
|
+
done: (err?: Error) => void
|
|
15
15
|
) => void
|
|
16
16
|
|
|
17
17
|
/**
|
package/types/reply.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface FastifyReply<
|
|
|
40
40
|
// Note: should consider refactoring the argument order for redirect. statusCode is optional so it should be after the required url param
|
|
41
41
|
redirect(statusCode: number, url: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
|
|
42
42
|
redirect(url: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
|
|
43
|
+
hijack(): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
|
|
43
44
|
callNotFound(): void;
|
|
44
45
|
getResponseTime(): number;
|
|
45
46
|
type(contentType: string): FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>;
|
package/types/route.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FastifyInstance } from './instance'
|
|
2
2
|
import { FastifyRequest, RequestGenericInterface } from './request'
|
|
3
3
|
import { FastifyReply, ReplyGenericInterface } from './reply'
|
|
4
|
-
import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError } from './schema'
|
|
4
|
+
import { FastifySchema, FastifySchemaCompiler, FastifySchemaValidationError, FastifySerializerCompiler } from './schema'
|
|
5
5
|
import { HTTPMethods, RawServerBase, RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, ContextConfigDefault } from './utils'
|
|
6
6
|
import { LogLevel } from './logger'
|
|
7
7
|
import { preValidationHookHandler, preHandlerHookHandler, preSerializationHookHandler, onRequestHookHandler, preParsingHookHandler, onResponseHookHandler, onSendHookHandler, onErrorHookHandler, onTimeoutHookHandler } from './hooks'
|
|
@@ -21,8 +21,9 @@ export interface RouteShorthandOptions<
|
|
|
21
21
|
> {
|
|
22
22
|
schema?: FastifySchema;
|
|
23
23
|
attachValidation?: boolean;
|
|
24
|
+
exposeHeadRoute?: boolean;
|
|
24
25
|
validatorCompiler?: FastifySchemaCompiler;
|
|
25
|
-
serializerCompiler?:
|
|
26
|
+
serializerCompiler?: FastifySerializerCompiler;
|
|
26
27
|
bodyLimit?: number;
|
|
27
28
|
logLevel?: LogLevel;
|
|
28
29
|
config?: ContextConfig;
|
|
@@ -121,3 +122,8 @@ export type RouteHandler<
|
|
|
121
122
|
request: FastifyRequest<RouteGeneric, RawServer, RawRequest>,
|
|
122
123
|
reply: FastifyReply<RawServer, RawRequest, RawReply, RouteGeneric, ContextConfig>
|
|
123
124
|
) => void | Promise<RouteGeneric['Reply'] | void>
|
|
125
|
+
|
|
126
|
+
export type DefaultRoute<Request, Reply> = (
|
|
127
|
+
req: Request,
|
|
128
|
+
res: Reply,
|
|
129
|
+
) => void;
|
package/types/schema.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface FastifySchemaValidationError {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface FastifyValidationResult {
|
|
29
|
+
(data: any): boolean | PromiseLike<any> | { error?: Error, value?: any }
|
|
29
30
|
errors?: FastifySchemaValidationError[] | null;
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -33,3 +34,5 @@ export interface FastifyValidationResult {
|
|
|
33
34
|
* Compiler for FastifySchema Type
|
|
34
35
|
*/
|
|
35
36
|
export type FastifySchemaCompiler = (routeSchema: FastifyRouteSchemaDef) => FastifyValidationResult
|
|
37
|
+
|
|
38
|
+
export type FastifySerializerCompiler = (routeSchema: FastifyRouteSchemaDef) => (data: any) => string
|