fastify 3.27.2 → 3.28.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/docs/Reference/Reply.md +49 -0
- package/fastify.js +1 -1
- package/lib/errors.js +8 -0
- package/lib/fourOhFour.js +20 -1
- package/lib/reply.js +96 -9
- package/lib/symbols.js +1 -0
- package/package.json +1 -1
- package/test/404s.test.js +52 -0
- package/test/internals/reply.test.js +16 -14
- package/test/logger.test.js +20 -0
- package/test/reply-trailers.test.js +277 -0
- package/test/types/hooks.test-d.ts +52 -2
- package/test/types/request.test-d.ts +41 -1
- package/types/.eslintrc.json +3 -1
- package/types/hooks.d.ts +85 -61
- package/types/instance.d.ts +54 -38
- package/types/request.d.ts +2 -1
- package/types/route.d.ts +35 -30
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const t = require('tap')
|
|
4
|
+
const test = t.test
|
|
5
|
+
const Fastify = require('..')
|
|
6
|
+
const { Readable } = require('stream')
|
|
7
|
+
const { createHash } = require('crypto')
|
|
8
|
+
|
|
9
|
+
test('send trailers when payload is empty string', t => {
|
|
10
|
+
t.plan(5)
|
|
11
|
+
|
|
12
|
+
const fastify = Fastify()
|
|
13
|
+
|
|
14
|
+
fastify.get('/', function (request, reply) {
|
|
15
|
+
reply.trailer('ETag', function (reply, payload) {
|
|
16
|
+
return 'custom-etag'
|
|
17
|
+
})
|
|
18
|
+
reply.send('')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
fastify.inject({
|
|
22
|
+
method: 'GET',
|
|
23
|
+
url: '/'
|
|
24
|
+
}, (error, res) => {
|
|
25
|
+
t.error(error)
|
|
26
|
+
t.equal(res.statusCode, 200)
|
|
27
|
+
t.equal(res.headers.trailer, 'etag')
|
|
28
|
+
t.equal(res.trailers.etag, 'custom-etag')
|
|
29
|
+
t.notHas(res.headers, 'content-length')
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('send trailers when payload is empty buffer', t => {
|
|
34
|
+
t.plan(5)
|
|
35
|
+
|
|
36
|
+
const fastify = Fastify()
|
|
37
|
+
|
|
38
|
+
fastify.get('/', function (request, reply) {
|
|
39
|
+
reply.trailer('ETag', function (reply, payload) {
|
|
40
|
+
return 'custom-etag'
|
|
41
|
+
})
|
|
42
|
+
reply.send(Buffer.alloc(0))
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
fastify.inject({
|
|
46
|
+
method: 'GET',
|
|
47
|
+
url: '/'
|
|
48
|
+
}, (error, res) => {
|
|
49
|
+
t.error(error)
|
|
50
|
+
t.equal(res.statusCode, 200)
|
|
51
|
+
t.equal(res.headers.trailer, 'etag')
|
|
52
|
+
t.equal(res.trailers.etag, 'custom-etag')
|
|
53
|
+
t.notHas(res.headers, 'content-length')
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('send trailers when payload is undefined', t => {
|
|
58
|
+
t.plan(5)
|
|
59
|
+
|
|
60
|
+
const fastify = Fastify()
|
|
61
|
+
|
|
62
|
+
fastify.get('/', function (request, reply) {
|
|
63
|
+
reply.trailer('ETag', function (reply, payload) {
|
|
64
|
+
return 'custom-etag'
|
|
65
|
+
})
|
|
66
|
+
reply.send(undefined)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
fastify.inject({
|
|
70
|
+
method: 'GET',
|
|
71
|
+
url: '/'
|
|
72
|
+
}, (error, res) => {
|
|
73
|
+
t.error(error)
|
|
74
|
+
t.equal(res.statusCode, 200)
|
|
75
|
+
t.equal(res.headers.trailer, 'etag')
|
|
76
|
+
t.equal(res.trailers.etag, 'custom-etag')
|
|
77
|
+
t.notHas(res.headers, 'content-length')
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('send trailers when payload is json', t => {
|
|
82
|
+
t.plan(7)
|
|
83
|
+
|
|
84
|
+
const fastify = Fastify()
|
|
85
|
+
const data = JSON.stringify({ hello: 'world' })
|
|
86
|
+
const hash = createHash('md5')
|
|
87
|
+
hash.update(data)
|
|
88
|
+
const md5 = hash.digest('hex')
|
|
89
|
+
|
|
90
|
+
fastify.get('/', function (request, reply) {
|
|
91
|
+
reply.trailer('Content-MD5', function (reply, payload) {
|
|
92
|
+
t.equal(data, payload)
|
|
93
|
+
const hash = createHash('md5')
|
|
94
|
+
hash.update(payload)
|
|
95
|
+
return hash.digest('hex')
|
|
96
|
+
})
|
|
97
|
+
reply.send(data)
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
fastify.inject({
|
|
101
|
+
method: 'GET',
|
|
102
|
+
url: '/'
|
|
103
|
+
}, (error, res) => {
|
|
104
|
+
t.error(error)
|
|
105
|
+
t.equal(res.statusCode, 200)
|
|
106
|
+
t.equal(res.headers['transfer-encoding'], 'chunked')
|
|
107
|
+
t.equal(res.headers.trailer, 'content-md5')
|
|
108
|
+
t.equal(res.trailers['content-md5'], md5)
|
|
109
|
+
t.notHas(res.headers, 'content-length')
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('send trailers when payload is stream', t => {
|
|
114
|
+
t.plan(7)
|
|
115
|
+
|
|
116
|
+
const fastify = Fastify()
|
|
117
|
+
|
|
118
|
+
fastify.get('/', function (request, reply) {
|
|
119
|
+
reply.trailer('ETag', function (reply, payload) {
|
|
120
|
+
t.same(payload, null)
|
|
121
|
+
return 'custom-etag'
|
|
122
|
+
})
|
|
123
|
+
const stream = Readable.from([JSON.stringify({ hello: 'world' })])
|
|
124
|
+
reply.send(stream)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
fastify.inject({
|
|
128
|
+
method: 'GET',
|
|
129
|
+
url: '/'
|
|
130
|
+
}, (error, res) => {
|
|
131
|
+
t.error(error)
|
|
132
|
+
t.equal(res.statusCode, 200)
|
|
133
|
+
t.equal(res.headers['transfer-encoding'], 'chunked')
|
|
134
|
+
t.equal(res.headers.trailer, 'etag')
|
|
135
|
+
t.equal(res.trailers.etag, 'custom-etag')
|
|
136
|
+
t.notHas(res.headers, 'content-length')
|
|
137
|
+
})
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('removeTrailer', t => {
|
|
141
|
+
t.plan(6)
|
|
142
|
+
|
|
143
|
+
const fastify = Fastify()
|
|
144
|
+
|
|
145
|
+
fastify.get('/', function (request, reply) {
|
|
146
|
+
reply.removeTrailer('ETag') // remove nothing
|
|
147
|
+
reply.trailer('ETag', function (reply, payload) {
|
|
148
|
+
return 'custom-etag'
|
|
149
|
+
})
|
|
150
|
+
reply.trailer('Should-Not-Call', function (reply, payload) {
|
|
151
|
+
t.fail('it should not called as this trailer is removed')
|
|
152
|
+
return 'should-not-call'
|
|
153
|
+
})
|
|
154
|
+
reply.removeTrailer('Should-Not-Call')
|
|
155
|
+
reply.send(undefined)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
fastify.inject({
|
|
159
|
+
method: 'GET',
|
|
160
|
+
url: '/'
|
|
161
|
+
}, (error, res) => {
|
|
162
|
+
t.error(error)
|
|
163
|
+
t.equal(res.statusCode, 200)
|
|
164
|
+
t.equal(res.headers.trailer, 'etag')
|
|
165
|
+
t.equal(res.trailers.etag, 'custom-etag')
|
|
166
|
+
t.notOk(res.trailers['should-not-call'])
|
|
167
|
+
t.notHas(res.headers, 'content-length')
|
|
168
|
+
})
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('hasTrailer', t => {
|
|
172
|
+
t.plan(10)
|
|
173
|
+
|
|
174
|
+
const fastify = Fastify()
|
|
175
|
+
|
|
176
|
+
fastify.get('/', function (request, reply) {
|
|
177
|
+
t.equal(reply.hasTrailer('ETag'), false)
|
|
178
|
+
reply.trailer('ETag', function (reply, payload) {
|
|
179
|
+
return 'custom-etag'
|
|
180
|
+
})
|
|
181
|
+
t.equal(reply.hasTrailer('ETag'), true)
|
|
182
|
+
reply.trailer('Should-Not-Call', function (reply, payload) {
|
|
183
|
+
t.fail('it should not called as this trailer is removed')
|
|
184
|
+
return 'should-not-call'
|
|
185
|
+
})
|
|
186
|
+
t.equal(reply.hasTrailer('Should-Not-Call'), true)
|
|
187
|
+
reply.removeTrailer('Should-Not-Call')
|
|
188
|
+
t.equal(reply.hasTrailer('Should-Not-Call'), false)
|
|
189
|
+
reply.send(undefined)
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
fastify.inject({
|
|
193
|
+
method: 'GET',
|
|
194
|
+
url: '/'
|
|
195
|
+
}, (error, res) => {
|
|
196
|
+
t.error(error)
|
|
197
|
+
t.equal(res.statusCode, 200)
|
|
198
|
+
t.equal(res.headers.trailer, 'etag')
|
|
199
|
+
t.equal(res.trailers.etag, 'custom-etag')
|
|
200
|
+
t.notOk(res.trailers['should-not-call'])
|
|
201
|
+
t.notHas(res.headers, 'content-length')
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test('throw error when trailer header name is not allowed', t => {
|
|
206
|
+
const INVALID_TRAILERS = [
|
|
207
|
+
'transfer-encoding',
|
|
208
|
+
'content-length',
|
|
209
|
+
'host',
|
|
210
|
+
'cache-control',
|
|
211
|
+
'max-forwards',
|
|
212
|
+
'te',
|
|
213
|
+
'authorization',
|
|
214
|
+
'set-cookie',
|
|
215
|
+
'content-encoding',
|
|
216
|
+
'content-type',
|
|
217
|
+
'content-range',
|
|
218
|
+
'trailer'
|
|
219
|
+
]
|
|
220
|
+
t.plan(INVALID_TRAILERS.length + 2)
|
|
221
|
+
|
|
222
|
+
const fastify = Fastify()
|
|
223
|
+
|
|
224
|
+
fastify.get('/', function (request, reply) {
|
|
225
|
+
for (const key of INVALID_TRAILERS) {
|
|
226
|
+
try {
|
|
227
|
+
reply.trailer(key, () => {})
|
|
228
|
+
} catch (err) {
|
|
229
|
+
t.equal(err.message, `Called reply.trailer with an invalid header name: ${key}`)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
reply.send('')
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
fastify.inject({
|
|
236
|
+
method: 'GET',
|
|
237
|
+
url: '/'
|
|
238
|
+
}, (error, res) => {
|
|
239
|
+
t.error(error)
|
|
240
|
+
t.equal(res.statusCode, 200)
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test('throw error when trailer header value is not function', t => {
|
|
245
|
+
const INVALID_TRAILERS_VALUE = [
|
|
246
|
+
undefined,
|
|
247
|
+
null,
|
|
248
|
+
true,
|
|
249
|
+
false,
|
|
250
|
+
'invalid',
|
|
251
|
+
[],
|
|
252
|
+
new Date(),
|
|
253
|
+
{}
|
|
254
|
+
]
|
|
255
|
+
t.plan(INVALID_TRAILERS_VALUE.length + 2)
|
|
256
|
+
|
|
257
|
+
const fastify = Fastify()
|
|
258
|
+
|
|
259
|
+
fastify.get('/', function (request, reply) {
|
|
260
|
+
for (const value of INVALID_TRAILERS_VALUE) {
|
|
261
|
+
try {
|
|
262
|
+
reply.trailer('invalid', value)
|
|
263
|
+
} catch (err) {
|
|
264
|
+
t.equal(err.message, `Called reply.trailer('invalid', fn) with an invalid type: ${typeof value}. Expected a function.`)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
reply.send('')
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
fastify.inject({
|
|
271
|
+
method: 'GET',
|
|
272
|
+
url: '/'
|
|
273
|
+
}, (error, res) => {
|
|
274
|
+
t.error(error)
|
|
275
|
+
t.equal(res.statusCode, 200)
|
|
276
|
+
})
|
|
277
|
+
})
|
|
@@ -9,9 +9,11 @@ import fastify, {
|
|
|
9
9
|
RawServerBase,
|
|
10
10
|
RouteOptions,
|
|
11
11
|
RegisterOptions,
|
|
12
|
-
FastifyPluginOptions
|
|
12
|
+
FastifyPluginOptions,
|
|
13
|
+
FastifyContextConfig, RawServerDefault
|
|
13
14
|
} from '../../fastify'
|
|
14
15
|
import { preHandlerAsyncHookHandler, RequestPayload } from '../../types/hooks'
|
|
16
|
+
import { RouteGenericInterface } from '../../types/route'
|
|
15
17
|
|
|
16
18
|
const server = fastify()
|
|
17
19
|
|
|
@@ -213,7 +215,7 @@ server.addHook('onClose', async (instance) => {
|
|
|
213
215
|
// Use case to monitor any regression on issue #3620
|
|
214
216
|
// ref.: https://github.com/fastify/fastify/issues/3620
|
|
215
217
|
const customTypedHook: preHandlerAsyncHookHandler<
|
|
216
|
-
|
|
218
|
+
RawServerDefault,
|
|
217
219
|
RawRequestDefaultExpression,
|
|
218
220
|
RawReplyDefaultExpression,
|
|
219
221
|
Record<string, unknown>
|
|
@@ -226,3 +228,51 @@ Record<string, unknown>
|
|
|
226
228
|
server.register(async (instance) => {
|
|
227
229
|
instance.addHook('preHandler', customTypedHook)
|
|
228
230
|
})
|
|
231
|
+
|
|
232
|
+
// Test custom Context Config types for hooks
|
|
233
|
+
type CustomContextConfig = FastifyContextConfig & {
|
|
234
|
+
foo: string;
|
|
235
|
+
bar: number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
server.route<RouteGenericInterface, CustomContextConfig>({
|
|
239
|
+
method: 'GET',
|
|
240
|
+
url: '/',
|
|
241
|
+
handler: () => {},
|
|
242
|
+
onRequest: (request, reply) => {
|
|
243
|
+
expectType<CustomContextConfig>(request.context.config)
|
|
244
|
+
expectType<CustomContextConfig>(reply.context.config)
|
|
245
|
+
},
|
|
246
|
+
preParsing: (request, reply) => {
|
|
247
|
+
expectType<CustomContextConfig>(request.context.config)
|
|
248
|
+
expectType<CustomContextConfig>(reply.context.config)
|
|
249
|
+
},
|
|
250
|
+
preValidation: (request, reply) => {
|
|
251
|
+
expectType<CustomContextConfig>(request.context.config)
|
|
252
|
+
expectType<CustomContextConfig>(reply.context.config)
|
|
253
|
+
},
|
|
254
|
+
preHandler: (request, reply) => {
|
|
255
|
+
expectType<CustomContextConfig>(request.context.config)
|
|
256
|
+
expectType<CustomContextConfig>(reply.context.config)
|
|
257
|
+
},
|
|
258
|
+
preSerialization: (request, reply) => {
|
|
259
|
+
expectType<CustomContextConfig>(request.context.config)
|
|
260
|
+
expectType<CustomContextConfig>(reply.context.config)
|
|
261
|
+
},
|
|
262
|
+
onSend: (request, reply) => {
|
|
263
|
+
expectType<CustomContextConfig>(request.context.config)
|
|
264
|
+
expectType<CustomContextConfig>(reply.context.config)
|
|
265
|
+
},
|
|
266
|
+
onResponse: (request, reply) => {
|
|
267
|
+
expectType<CustomContextConfig>(request.context.config)
|
|
268
|
+
expectType<CustomContextConfig>(reply.context.config)
|
|
269
|
+
},
|
|
270
|
+
onTimeout: (request, reply) => {
|
|
271
|
+
expectType<CustomContextConfig>(request.context.config)
|
|
272
|
+
expectType<CustomContextConfig>(reply.context.config)
|
|
273
|
+
},
|
|
274
|
+
onError: (request, reply) => {
|
|
275
|
+
expectType<CustomContextConfig>(request.context.config)
|
|
276
|
+
expectType<CustomContextConfig>(reply.context.config)
|
|
277
|
+
}
|
|
278
|
+
})
|
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
import { expectType } from 'tsd'
|
|
2
|
-
import fastify, {
|
|
2
|
+
import fastify, {
|
|
3
|
+
RouteHandler,
|
|
4
|
+
RawRequestDefaultExpression,
|
|
5
|
+
RequestBodyDefault,
|
|
6
|
+
RequestGenericInterface,
|
|
7
|
+
FastifyContext,
|
|
8
|
+
ContextConfigDefault,
|
|
9
|
+
FastifyContextConfig,
|
|
10
|
+
FastifyLogFn,
|
|
11
|
+
RawServerDefault,
|
|
12
|
+
RawReplyDefaultExpression,
|
|
13
|
+
RouteHandlerMethod
|
|
14
|
+
} from '../../fastify'
|
|
3
15
|
import { RequestParamsDefault, RequestHeadersDefault, RequestQuerystringDefault } from '../../types/utils'
|
|
4
16
|
import { FastifyLoggerInstance } from '../../types/logger'
|
|
5
17
|
import { FastifyRequest } from '../../types/request'
|
|
6
18
|
import { FastifyReply } from '../../types/reply'
|
|
7
19
|
import { FastifyInstance } from '../../types/instance'
|
|
20
|
+
import { RouteGenericInterface } from '../../types/route'
|
|
8
21
|
|
|
9
22
|
interface RequestBody {
|
|
10
23
|
content: string;
|
|
@@ -38,6 +51,10 @@ type CustomRequest = FastifyRequest<{
|
|
|
38
51
|
Headers: RequestHeaders;
|
|
39
52
|
}>
|
|
40
53
|
|
|
54
|
+
interface CustomLoggerInterface extends FastifyLoggerInstance {
|
|
55
|
+
foo: FastifyLogFn; // custom severity logger method
|
|
56
|
+
}
|
|
57
|
+
|
|
41
58
|
const getHandler: RouteHandler = function (request, _reply) {
|
|
42
59
|
expectType<string>(request.url)
|
|
43
60
|
expectType<string>(request.method)
|
|
@@ -64,6 +81,10 @@ const getHandler: RouteHandler = function (request, _reply) {
|
|
|
64
81
|
expectType<FastifyInstance>(request.server)
|
|
65
82
|
}
|
|
66
83
|
|
|
84
|
+
const getHandlerWithCustomLogger: RouteHandlerMethod<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, CustomLoggerInterface> = function (request, _reply) {
|
|
85
|
+
expectType<CustomLoggerInterface>(request.log)
|
|
86
|
+
}
|
|
87
|
+
|
|
67
88
|
const postHandler: Handler = function (request) {
|
|
68
89
|
expectType<RequestBody>(request.body)
|
|
69
90
|
expectType<RequestParams>(request.params)
|
|
@@ -96,3 +117,22 @@ const server = fastify()
|
|
|
96
117
|
server.get('/get', getHandler)
|
|
97
118
|
server.post('/post', postHandler)
|
|
98
119
|
server.put('/put', putHandler)
|
|
120
|
+
|
|
121
|
+
const customLogger: CustomLoggerInterface = {
|
|
122
|
+
info: () => { },
|
|
123
|
+
warn: () => { },
|
|
124
|
+
error: () => { },
|
|
125
|
+
fatal: () => { },
|
|
126
|
+
trace: () => { },
|
|
127
|
+
debug: () => { },
|
|
128
|
+
foo: () => { }, // custom severity logger method
|
|
129
|
+
child: () => customLogger
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const serverWithCustomLogger = fastify({ logger: customLogger })
|
|
133
|
+
expectType<
|
|
134
|
+
FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>
|
|
135
|
+
& PromiseLike<FastifyInstance<RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, CustomLoggerInterface>>
|
|
136
|
+
>(serverWithCustomLogger)
|
|
137
|
+
|
|
138
|
+
serverWithCustomLogger.get('/get', getHandlerWithCustomLogger)
|
package/types/.eslintrc.json
CHANGED
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
"@typescript-eslint/explicit-function-return-type": "off",
|
|
37
37
|
"@typescript-eslint/no-unused-vars": "off",
|
|
38
38
|
"@typescript-eslint/no-non-null-assertion": "off",
|
|
39
|
-
"@typescript-eslint/no-misused-promises": ["error"
|
|
39
|
+
"@typescript-eslint/no-misused-promises": ["error", {
|
|
40
|
+
"checksVoidReturn": false
|
|
41
|
+
}]
|
|
40
42
|
},
|
|
41
43
|
"globals": {
|
|
42
44
|
"NodeJS": "readonly"
|