fastify 4.9.2 → 4.10.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.
@@ -5,6 +5,8 @@ const test = t.test
5
5
  const Fastify = require('..')
6
6
  const { Readable } = require('stream')
7
7
  const { createHash } = require('crypto')
8
+ const { promisify } = require('util')
9
+ const sleep = promisify(setTimeout)
8
10
 
9
11
  test('send trailers when payload is empty string', t => {
10
12
  t.plan(5)
@@ -12,8 +14,8 @@ test('send trailers when payload is empty string', t => {
12
14
  const fastify = Fastify()
13
15
 
14
16
  fastify.get('/', function (request, reply) {
15
- reply.trailer('ETag', function (reply, payload) {
16
- return 'custom-etag'
17
+ reply.trailer('ETag', function (reply, payload, done) {
18
+ done(null, 'custom-etag')
17
19
  })
18
20
  reply.send('')
19
21
  })
@@ -36,8 +38,8 @@ test('send trailers when payload is empty buffer', t => {
36
38
  const fastify = Fastify()
37
39
 
38
40
  fastify.get('/', function (request, reply) {
39
- reply.trailer('ETag', function (reply, payload) {
40
- return 'custom-etag'
41
+ reply.trailer('ETag', function (reply, payload, done) {
42
+ done(null, 'custom-etag')
41
43
  })
42
44
  reply.send(Buffer.alloc(0))
43
45
  })
@@ -60,8 +62,8 @@ test('send trailers when payload is undefined', t => {
60
62
  const fastify = Fastify()
61
63
 
62
64
  fastify.get('/', function (request, reply) {
63
- reply.trailer('ETag', function (reply, payload) {
64
- return 'custom-etag'
65
+ reply.trailer('ETag', function (reply, payload, done) {
66
+ done(null, 'custom-etag')
65
67
  })
66
68
  reply.send(undefined)
67
69
  })
@@ -88,11 +90,11 @@ test('send trailers when payload is json', t => {
88
90
  const md5 = hash.digest('hex')
89
91
 
90
92
  fastify.get('/', function (request, reply) {
91
- reply.trailer('Content-MD5', function (reply, payload) {
93
+ reply.trailer('Content-MD5', function (reply, payload, done) {
92
94
  t.equal(data, payload)
93
95
  const hash = createHash('md5')
94
96
  hash.update(payload)
95
- return hash.digest('hex')
97
+ done(null, hash.digest('hex'))
96
98
  })
97
99
  reply.send(data)
98
100
  })
@@ -116,9 +118,9 @@ test('send trailers when payload is stream', t => {
116
118
  const fastify = Fastify()
117
119
 
118
120
  fastify.get('/', function (request, reply) {
119
- reply.trailer('ETag', function (reply, payload) {
121
+ reply.trailer('ETag', function (reply, payload, done) {
120
122
  t.same(payload, null)
121
- return 'custom-etag'
123
+ done(null, 'custom-etag')
122
124
  })
123
125
  const stream = Readable.from([JSON.stringify({ hello: 'world' })])
124
126
  reply.send(stream)
@@ -137,6 +139,161 @@ test('send trailers when payload is stream', t => {
137
139
  })
138
140
  })
139
141
 
142
+ test('send trailers when using async-await', t => {
143
+ t.plan(5)
144
+
145
+ const fastify = Fastify()
146
+
147
+ fastify.get('/', function (request, reply) {
148
+ reply.trailer('ETag', async function (reply, payload) {
149
+ return 'custom-etag'
150
+ })
151
+ reply.send('')
152
+ })
153
+
154
+ fastify.inject({
155
+ method: 'GET',
156
+ url: '/'
157
+ }, (error, res) => {
158
+ t.error(error)
159
+ t.equal(res.statusCode, 200)
160
+ t.equal(res.headers.trailer, 'etag')
161
+ t.equal(res.trailers.etag, 'custom-etag')
162
+ t.notHas(res.headers, 'content-length')
163
+ })
164
+ })
165
+
166
+ test('error in trailers should be ignored', t => {
167
+ t.plan(5)
168
+
169
+ const fastify = Fastify()
170
+
171
+ fastify.get('/', function (request, reply) {
172
+ reply.trailer('ETag', function (reply, payload, done) {
173
+ done('error')
174
+ })
175
+ reply.send('')
176
+ })
177
+
178
+ fastify.inject({
179
+ method: 'GET',
180
+ url: '/'
181
+ }, (error, res) => {
182
+ t.error(error)
183
+ t.equal(res.statusCode, 200)
184
+ t.equal(res.headers.trailer, 'etag')
185
+ t.notHas(res.trailers, 'etag')
186
+ t.notHas(res.headers, 'content-length')
187
+ })
188
+ })
189
+
190
+ test('should emit deprecation warning when using direct return', t => {
191
+ t.plan(7)
192
+
193
+ const fastify = Fastify()
194
+
195
+ fastify.get('/', function (request, reply) {
196
+ reply.trailer('ETag', function (reply, payload) {
197
+ return 'custom-etag'
198
+ })
199
+ reply.send('')
200
+ })
201
+
202
+ process.on('warning', onWarning)
203
+ function onWarning (warning) {
204
+ t.equal(warning.name, 'FastifyDeprecation')
205
+ t.equal(warning.code, 'FSTDEP013')
206
+ }
207
+ t.teardown(() => process.removeListener('warning', onWarning))
208
+
209
+ fastify.inject({
210
+ method: 'GET',
211
+ url: '/'
212
+ }, (error, res) => {
213
+ t.error(error)
214
+ t.equal(res.statusCode, 200)
215
+ t.equal(res.headers.trailer, 'etag')
216
+ t.equal(res.trailers.etag, 'custom-etag')
217
+ t.notHas(res.headers, 'content-length')
218
+ })
219
+ })
220
+
221
+ test('trailer handler counter', t => {
222
+ t.plan(2)
223
+
224
+ const data = JSON.stringify({ hello: 'world' })
225
+ const hash = createHash('md5')
226
+ hash.update(data)
227
+ const md5 = hash.digest('hex')
228
+
229
+ t.test('callback with timeout', t => {
230
+ t.plan(9)
231
+ const fastify = Fastify()
232
+
233
+ fastify.get('/', function (request, reply) {
234
+ reply.trailer('Return-Early', function (reply, payload, done) {
235
+ t.equal(data, payload)
236
+ done(null, 'return')
237
+ })
238
+ reply.trailer('Content-MD5', function (reply, payload, done) {
239
+ t.equal(data, payload)
240
+ const hash = createHash('md5')
241
+ hash.update(payload)
242
+ setTimeout(() => {
243
+ done(null, hash.digest('hex'))
244
+ }, 500)
245
+ })
246
+ reply.send(data)
247
+ })
248
+
249
+ fastify.inject({
250
+ method: 'GET',
251
+ url: '/'
252
+ }, (error, res) => {
253
+ t.error(error)
254
+ t.equal(res.statusCode, 200)
255
+ t.equal(res.headers['transfer-encoding'], 'chunked')
256
+ t.equal(res.headers.trailer, 'return-early content-md5')
257
+ t.equal(res.trailers['return-early'], 'return')
258
+ t.equal(res.trailers['content-md5'], md5)
259
+ t.notHas(res.headers, 'content-length')
260
+ })
261
+ })
262
+
263
+ t.test('async-await', t => {
264
+ t.plan(9)
265
+ const fastify = Fastify()
266
+
267
+ fastify.get('/', function (request, reply) {
268
+ reply.trailer('Return-Early', async function (reply, payload) {
269
+ t.equal(data, payload)
270
+ return 'return'
271
+ })
272
+ reply.trailer('Content-MD5', async function (reply, payload) {
273
+ t.equal(data, payload)
274
+ const hash = createHash('md5')
275
+ hash.update(payload)
276
+ await sleep(500)
277
+ return hash.digest('hex')
278
+ })
279
+ reply.send(data)
280
+ })
281
+
282
+ fastify.inject({
283
+ method: 'GET',
284
+ url: '/'
285
+ }, (error, res) => {
286
+ t.error(error)
287
+ t.equal(res.statusCode, 200)
288
+ t.equal(res.headers['transfer-encoding'], 'chunked')
289
+ t.equal(res.headers.trailer, 'return-early content-md5')
290
+ t.equal(res.trailers['return-early'], 'return')
291
+ t.equal(res.trailers['content-md5'], md5)
292
+ t.notHas(res.headers, 'content-length')
293
+ })
294
+ })
295
+ })
296
+
140
297
  test('removeTrailer', t => {
141
298
  t.plan(6)
142
299
 
@@ -144,12 +301,12 @@ test('removeTrailer', t => {
144
301
 
145
302
  fastify.get('/', function (request, reply) {
146
303
  reply.removeTrailer('ETag') // remove nothing
147
- reply.trailer('ETag', function (reply, payload) {
148
- return 'custom-etag'
304
+ reply.trailer('ETag', function (reply, payload, done) {
305
+ done(null, 'custom-etag')
149
306
  })
150
- reply.trailer('Should-Not-Call', function (reply, payload) {
307
+ reply.trailer('Should-Not-Call', function (reply, payload, done) {
151
308
  t.fail('it should not called as this trailer is removed')
152
- return 'should-not-call'
309
+ done(null, 'should-not-call')
153
310
  })
154
311
  reply.removeTrailer('Should-Not-Call')
155
312
  reply.send(undefined)
@@ -168,6 +325,38 @@ test('removeTrailer', t => {
168
325
  })
169
326
  })
170
327
 
328
+ test('remove all trailers', t => {
329
+ t.plan(6)
330
+
331
+ const fastify = Fastify()
332
+
333
+ fastify.get('/', function (request, reply) {
334
+ reply.trailer('ETag', function (reply, payload, done) {
335
+ t.fail('it should not called as this trailer is removed')
336
+ done(null, 'custom-etag')
337
+ })
338
+ reply.removeTrailer('ETag')
339
+ reply.trailer('Should-Not-Call', function (reply, payload, done) {
340
+ t.fail('it should not called as this trailer is removed')
341
+ done(null, 'should-not-call')
342
+ })
343
+ reply.removeTrailer('Should-Not-Call')
344
+ reply.send('')
345
+ })
346
+
347
+ fastify.inject({
348
+ method: 'GET',
349
+ url: '/'
350
+ }, (error, res) => {
351
+ t.error(error)
352
+ t.equal(res.statusCode, 200)
353
+ t.notOk(res.headers.trailer)
354
+ t.notOk(res.trailers.etag)
355
+ t.notOk(res.trailers['should-not-call'])
356
+ t.notHas(res.headers, 'content-length')
357
+ })
358
+ })
359
+
171
360
  test('hasTrailer', t => {
172
361
  t.plan(10)
173
362
 
@@ -175,13 +364,13 @@ test('hasTrailer', t => {
175
364
 
176
365
  fastify.get('/', function (request, reply) {
177
366
  t.equal(reply.hasTrailer('ETag'), false)
178
- reply.trailer('ETag', function (reply, payload) {
179
- return 'custom-etag'
367
+ reply.trailer('ETag', function (reply, payload, done) {
368
+ done(null, 'custom-etag')
180
369
  })
181
370
  t.equal(reply.hasTrailer('ETag'), true)
182
- reply.trailer('Should-Not-Call', function (reply, payload) {
371
+ reply.trailer('Should-Not-Call', function (reply, payload, done) {
183
372
  t.fail('it should not called as this trailer is removed')
184
- return 'should-not-call'
373
+ done(null, 'should-not-call')
185
374
  })
186
375
  t.equal(reply.hasTrailer('Should-Not-Call'), true)
187
376
  reply.removeTrailer('Should-Not-Call')
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { connect } = require('net')
4
+ const sget = require('simple-get').concat
4
5
  const t = require('tap')
5
6
  const test = t.test
6
7
  const Fastify = require('..')
@@ -313,3 +314,98 @@ test('default clientError replies with bad request on reused keep-alive connecti
313
314
  client.write('\r\n\r\n')
314
315
  })
315
316
  })
317
+
318
+ test('request.routeOptions should be immutable', t => {
319
+ t.plan(14)
320
+ const fastify = Fastify()
321
+ const handler = function (req, res) {
322
+ t.equal('POST', req.routeOptions.method)
323
+ t.equal('/', req.routeOptions.url)
324
+ t.throws(() => { req.routeOptions = null }, new TypeError('Cannot set property routeOptions of #<Request> which has only a getter'))
325
+ t.throws(() => { req.routeOptions.method = 'INVALID' }, new TypeError('Cannot assign to read only property \'method\' of object \'#<Object>\''))
326
+ t.throws(() => { req.routeOptions.url = '//' }, new TypeError('Cannot assign to read only property \'url\' of object \'#<Object>\''))
327
+ t.throws(() => { req.routeOptions.bodyLimit = 0xDEADBEEF }, new TypeError('Cannot assign to read only property \'bodyLimit\' of object \'#<Object>\''))
328
+ t.throws(() => { req.routeOptions.attachValidation = true }, new TypeError('Cannot assign to read only property \'attachValidation\' of object \'#<Object>\''))
329
+ t.throws(() => { req.routeOptions.logLevel = 'invalid' }, new TypeError('Cannot assign to read only property \'logLevel\' of object \'#<Object>\''))
330
+ t.throws(() => { req.routeOptions.version = '95.0.1' }, new TypeError('Cannot assign to read only property \'version\' of object \'#<Object>\''))
331
+ t.throws(() => { req.routeOptions.prefixTrailingSlash = true }, new TypeError('Cannot assign to read only property \'prefixTrailingSlash\' of object \'#<Object>\''))
332
+ t.throws(() => { req.routeOptions.newAttribute = {} }, new TypeError('Cannot add property newAttribute, object is not extensible'))
333
+
334
+ for (const key of Object.keys(req.routeOptions)) {
335
+ if (typeof req.routeOptions[key] === 'object' && req.routeOptions[key] !== null) {
336
+ t.fail('Object.freeze must run recursively on nested structures to ensure that routeOptions is immutable.')
337
+ }
338
+ }
339
+
340
+ res.send({})
341
+ }
342
+ fastify.post('/', {
343
+ bodyLimit: 1000,
344
+ handler
345
+ })
346
+ fastify.listen({ port: 0 }, function (err) {
347
+ t.error(err)
348
+ t.teardown(() => { fastify.close() })
349
+
350
+ sget({
351
+ method: 'POST',
352
+ url: 'http://localhost:' + fastify.server.address().port,
353
+ headers: { 'Content-Type': 'application/json' },
354
+ body: [],
355
+ json: true
356
+ }, (err, response, body) => {
357
+ t.error(err)
358
+ t.equal(response.statusCode, 200)
359
+ })
360
+ })
361
+ })
362
+
363
+ test('test request.routeOptions.version', t => {
364
+ t.plan(7)
365
+ const fastify = Fastify()
366
+
367
+ fastify.route({
368
+ method: 'POST',
369
+ url: '/version',
370
+ constraints: { version: '1.2.0' },
371
+ handler: function (request, reply) {
372
+ t.equal('1.2.0', request.routeOptions.version)
373
+ reply.send({})
374
+ }
375
+ })
376
+
377
+ fastify.route({
378
+ method: 'POST',
379
+ url: '/version-undefined',
380
+ handler: function (request, reply) {
381
+ t.equal(undefined, request.routeOptions.version)
382
+ reply.send({})
383
+ }
384
+ })
385
+ fastify.listen({ port: 0 }, function (err) {
386
+ t.error(err)
387
+ t.teardown(() => { fastify.close() })
388
+
389
+ sget({
390
+ method: 'POST',
391
+ url: 'http://localhost:' + fastify.server.address().port + '/version',
392
+ headers: { 'Content-Type': 'application/json', 'Accept-Version': '1.2.0' },
393
+ body: [],
394
+ json: true
395
+ }, (err, response, body) => {
396
+ t.error(err)
397
+ t.equal(response.statusCode, 200)
398
+ })
399
+
400
+ sget({
401
+ method: 'POST',
402
+ url: 'http://localhost:' + fastify.server.address().port + '/version-undefined',
403
+ headers: { 'Content-Type': 'application/json' },
404
+ body: [],
405
+ json: true
406
+ }, (err, response, body) => {
407
+ t.error(err)
408
+ t.equal(response.statusCode, 200)
409
+ })
410
+ })
411
+ })
@@ -36,19 +36,18 @@ test('listen should accept stringified number port', t => {
36
36
 
37
37
  test('listen should reject string port', async (t) => {
38
38
  t.plan(2)
39
-
40
39
  const fastify = Fastify()
41
40
  t.teardown(fastify.close.bind(fastify))
42
41
 
43
42
  try {
44
43
  await fastify.listen({ port: 'hello-world' })
45
44
  } catch (error) {
46
- t.same(error.message, 'options.port should be >= 0 and < 65536. Received hello-world.')
45
+ t.equal(error.code, 'ERR_SOCKET_BAD_PORT')
47
46
  }
48
47
 
49
48
  try {
50
49
  await fastify.listen({ port: '1234hello' })
51
50
  } catch (error) {
52
- t.same(error.message, 'options.port should be >= 0 and < 65536. Received 1234hello.')
51
+ t.equal(error.code, 'ERR_SOCKET_BAD_PORT')
53
52
  }
54
53
  })
@@ -10,7 +10,8 @@ import fastify, {
10
10
  InjectOptions, FastifyBaseLogger,
11
11
  RouteGenericInterface,
12
12
  ValidationResult,
13
- FastifyErrorCodes
13
+ FastifyErrorCodes,
14
+ FastifyError
14
15
  } from '../../fastify'
15
16
  import { ErrorObject as AjvErrorObject } from 'ajv'
16
17
  import * as http from 'http'
@@ -235,6 +236,12 @@ const ajvErrorObject: AjvErrorObject = {
235
236
  }
236
237
  expectAssignable<ValidationResult>(ajvErrorObject)
237
238
 
239
+ expectAssignable<FastifyError['validation']>([ajvErrorObject])
240
+ expectAssignable<FastifyError['validationContext']>('body')
241
+ expectAssignable<FastifyError['validationContext']>('headers')
242
+ expectAssignable<FastifyError['validationContext']>('parameters')
243
+ expectAssignable<FastifyError['validationContext']>('querystring')
244
+
238
245
  const routeGeneric: RouteGenericInterface = {}
239
246
  expectType<unknown>(routeGeneric.Body)
240
247
  expectType<unknown>(routeGeneric.Headers)
@@ -123,35 +123,6 @@ const serverAutoInferredFileOption = fastify({
123
123
 
124
124
  expectType<FastifyBaseLogger>(serverAutoInferredFileOption.log)
125
125
 
126
- const serverAutoInferredPinoPrettyBooleanOption = fastify({
127
- logger: {
128
- prettyPrint: true
129
- }
130
- })
131
-
132
- expectType<FastifyBaseLogger>(serverAutoInferredPinoPrettyBooleanOption.log)
133
-
134
- const serverAutoInferredPinoPrettyObjectOption = fastify({
135
- logger: {
136
- prettyPrint: {
137
- translateTime: true,
138
- levelFirst: false,
139
- messageKey: 'msg',
140
- timestampKey: 'time',
141
- messageFormat: false,
142
- colorize: true,
143
- crlf: false,
144
- errorLikeObjectKeys: ['err', 'error'],
145
- errorProps: '',
146
- search: 'foo == `bar`',
147
- ignore: 'pid,hostname',
148
- suppressFlushSyncWarning: true
149
- }
150
- }
151
- })
152
-
153
- expectType<FastifyBaseLogger>(serverAutoInferredPinoPrettyObjectOption.log)
154
-
155
126
  const serverAutoInferredSerializerObjectOption = fastify({
156
127
  logger: {
157
128
  serializers: {
@@ -200,9 +171,6 @@ const passPinoOption = fastify({
200
171
  redact: ['custom'],
201
172
  messageKey: 'msg',
202
173
  nestedKey: 'nested',
203
- prettyPrint: {
204
-
205
- },
206
174
  enabled: true
207
175
  }
208
176
  })
@@ -215,6 +183,7 @@ expectDeprecated({} as FastifyLoggerInstance)
215
183
  const childParent = fastify().log
216
184
  // we test different option variant here
217
185
  expectType<FastifyLoggerInstance>(childParent.child({}, { level: 'info' }))
186
+ expectType<FastifyLoggerInstance>(childParent.child({}, { level: 'silent' }))
218
187
  expectType<FastifyLoggerInstance>(childParent.child({}, { redact: ['pass', 'pin'] }))
219
188
  expectType<FastifyLoggerInstance>(childParent.child({}, { serializers: { key: () => {} } }))
220
189
  expectType<FastifyLoggerInstance>(childParent.child({}, { level: 'info', redact: ['pass', 'pin'], serializers: { key: () => {} } }))
@@ -17,7 +17,7 @@ import fastify, {
17
17
  } from '../../fastify'
18
18
  import { RequestParamsDefault, RequestHeadersDefault, RequestQuerystringDefault } from '../../types/utils'
19
19
  import { FastifyLoggerInstance } from '../../types/logger'
20
- import { FastifyRequest } from '../../types/request'
20
+ import { FastifyRequest, RequestRouteOptions } from '../../types/request'
21
21
  import { FastifyReply } from '../../types/reply'
22
22
  import { FastifyInstance } from '../../types/instance'
23
23
  import { RouteGenericInterface } from '../../types/route'
@@ -66,6 +66,7 @@ const getHandler: RouteHandler = function (request, _reply) {
66
66
  expectType<string>(request.method)
67
67
  expectType<string>(request.routerPath)
68
68
  expectType<string>(request.routerMethod)
69
+ expectType<Readonly<RequestRouteOptions>>(request.routeOptions)
69
70
  expectType<boolean>(request.is404)
70
71
  expectType<string>(request.hostname)
71
72
  expectType<string>(request.ip)