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.
@@ -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
- RawServerBase,
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, { RouteHandler, RawRequestDefaultExpression, RequestBodyDefault, RequestGenericInterface, FastifyContext, ContextConfigDefault, FastifyContextConfig } from '../../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)
@@ -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"