fastify 4.23.2 → 4.24.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.
Files changed (44) hide show
  1. package/README.md +1 -1
  2. package/docs/Guides/Ecosystem.md +6 -0
  3. package/docs/Reference/Hooks.md +1 -0
  4. package/docs/Reference/Plugins.md +1 -1
  5. package/docs/Reference/Reply.md +4 -3
  6. package/docs/Reference/Request.md +3 -2
  7. package/docs/Reference/Server.md +31 -3
  8. package/docs/Reference/Type-Providers.md +2 -2
  9. package/docs/Reference/TypeScript.md +21 -7
  10. package/fastify.d.ts +2 -2
  11. package/fastify.js +8 -1
  12. package/lib/contentTypeParser.js +1 -1
  13. package/lib/reply.js +20 -3
  14. package/lib/reqIdGenFactory.js +15 -9
  15. package/lib/request.js +1 -1
  16. package/lib/route.js +18 -3
  17. package/lib/schemas.js +3 -3
  18. package/lib/warnings.js +3 -1
  19. package/package.json +2 -2
  20. package/test/async-dispose.test.js +21 -0
  21. package/test/constrained-routes.test.js +127 -3
  22. package/test/hooks-async.test.js +160 -10
  23. package/test/input-validation.js +3 -3
  24. package/test/internals/reply.test.js +33 -4
  25. package/test/logger/instantiation.test.js +338 -0
  26. package/test/logger/logger-test-utils.js +47 -0
  27. package/test/logger/logging.test.js +406 -0
  28. package/test/logger/options.test.js +500 -0
  29. package/test/logger/request.test.js +292 -0
  30. package/test/logger/response.test.js +184 -0
  31. package/test/reply-code.test.js +64 -0
  32. package/test/types/reply.test-d.ts +3 -3
  33. package/test/types/request.test-d.ts +9 -9
  34. package/test/types/type-provider.test-d.ts +89 -0
  35. package/test/types/using.test-d.ts +14 -0
  36. package/types/context.d.ts +9 -2
  37. package/types/instance.d.ts +4 -1
  38. package/types/plugin.d.ts +2 -1
  39. package/types/reply.d.ts +2 -2
  40. package/types/request.d.ts +3 -3
  41. package/types/route.d.ts +5 -5
  42. package/test/serial/logger.0.test.js +0 -866
  43. package/test/serial/logger.1.test.js +0 -862
  44. /package/test/{serial → logger}/tap-parallel-not-ok +0 -0
@@ -256,6 +256,91 @@ test('Should allow registering custom constrained routes outside constructor', t
256
256
  })
257
257
  })
258
258
 
259
+ test('Custom constrained routes registered also for HEAD method generated by fastify', t => {
260
+ t.plan(3)
261
+
262
+ const constraint = {
263
+ name: 'secret',
264
+ storage: function () {
265
+ const secrets = {}
266
+ return {
267
+ get: (secret) => { return secrets[secret] || null },
268
+ set: (secret, store) => { secrets[secret] = store }
269
+ }
270
+ },
271
+ deriveConstraint: (req, ctx) => {
272
+ return req.headers['x-secret']
273
+ },
274
+ validate () { return true }
275
+ }
276
+
277
+ const fastify = Fastify({ constraints: { secret: constraint } })
278
+
279
+ fastify.route({
280
+ method: 'GET',
281
+ url: '/',
282
+ constraints: { secret: 'mySecret' },
283
+ handler: (req, reply) => {
284
+ reply.send('from mySecret - my length is 31')
285
+ }
286
+ })
287
+
288
+ fastify.inject({
289
+ method: 'HEAD',
290
+ url: '/',
291
+ headers: {
292
+ 'X-Secret': 'mySecret'
293
+ }
294
+ }, (err, res) => {
295
+ t.error(err)
296
+ t.same(res.headers['content-length'], '31')
297
+ t.equal(res.statusCode, 200)
298
+ })
299
+ })
300
+
301
+ test('Custom constrained routes registered with addConstraintStrategy apply also for HEAD method generated by fastify', t => {
302
+ t.plan(3)
303
+
304
+ const constraint = {
305
+ name: 'secret',
306
+ storage: function () {
307
+ const secrets = {}
308
+ return {
309
+ get: (secret) => { return secrets[secret] || null },
310
+ set: (secret, store) => { secrets[secret] = store }
311
+ }
312
+ },
313
+ deriveConstraint: (req, ctx) => {
314
+ return req.headers['x-secret']
315
+ },
316
+ validate () { return true }
317
+ }
318
+
319
+ const fastify = Fastify()
320
+ fastify.addConstraintStrategy(constraint)
321
+
322
+ fastify.route({
323
+ method: 'GET',
324
+ url: '/',
325
+ constraints: { secret: 'mySecret' },
326
+ handler: (req, reply) => {
327
+ reply.send('from mySecret - my length is 31')
328
+ }
329
+ })
330
+
331
+ fastify.inject({
332
+ method: 'HEAD',
333
+ url: '/',
334
+ headers: {
335
+ 'X-Secret': 'mySecret'
336
+ }
337
+ }, (err, res) => {
338
+ t.error(err)
339
+ t.same(res.headers['content-length'], '31')
340
+ t.equal(res.statusCode, 200)
341
+ })
342
+ })
343
+
259
344
  test('Add a constraint strategy after fastify instance was started', t => {
260
345
  t.plan(4)
261
346
 
@@ -561,7 +646,7 @@ test('Should allow registering a constrained GET route after an unconstrained HE
561
646
  url: '/',
562
647
  handler: (req, reply) => {
563
648
  reply.header('content-type', 'text/plain')
564
- reply.send('custom HEAD response')
649
+ reply.send('HEAD response: length is about 33')
565
650
  }
566
651
  })
567
652
 
@@ -570,7 +655,8 @@ test('Should allow registering a constrained GET route after an unconstrained HE
570
655
  url: '/',
571
656
  constraints: { host: 'fastify.io' },
572
657
  handler: (req, reply) => {
573
- reply.send({ hello: 'from any other domain' })
658
+ reply.header('content-type', 'text/plain')
659
+ reply.send('Hello from constrains: length is about 41')
574
660
  }
575
661
  })
576
662
 
@@ -582,7 +668,7 @@ test('Should allow registering a constrained GET route after an unconstrained HE
582
668
  }
583
669
  }, (err, res) => {
584
670
  t.error(err)
585
- t.same(res.payload, 'custom HEAD response')
671
+ t.same(res.headers['content-length'], '41')
586
672
  t.equal(res.statusCode, 200)
587
673
  })
588
674
  })
@@ -774,3 +860,41 @@ test('error in async constraints', async (t) => {
774
860
  t.equal(statusCode, 500)
775
861
  }
776
862
  })
863
+
864
+ test('Allow regex constraints in routes', t => {
865
+ t.plan(5)
866
+
867
+ const fastify = Fastify()
868
+
869
+ fastify.route({
870
+ method: 'GET',
871
+ url: '/',
872
+ constraints: { host: /.*\.fastify\.io/ },
873
+ handler: (req, reply) => {
874
+ reply.send({ hello: 'from fastify dev domain' })
875
+ }
876
+ })
877
+
878
+ fastify.inject({
879
+ method: 'GET',
880
+ url: '/',
881
+ headers: {
882
+ host: 'dev.fastify.io'
883
+ }
884
+ }, (err, res) => {
885
+ t.error(err)
886
+ t.same(JSON.parse(res.payload), { hello: 'from fastify dev domain' })
887
+ t.equal(res.statusCode, 200)
888
+ })
889
+
890
+ fastify.inject({
891
+ method: 'GET',
892
+ url: '/',
893
+ headers: {
894
+ host: 'google.com'
895
+ }
896
+ }, (err, res) => {
897
+ t.error(err)
898
+ t.equal(res.statusCode, 404)
899
+ })
900
+ })
@@ -745,8 +745,8 @@ test('Should log a warning if is an async function with `done`', t => {
745
745
  try {
746
746
  fastify.addHook('onRequestAbort', async (req, done) => {})
747
747
  } catch (e) {
748
- t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
749
- t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
748
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
749
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
750
750
  }
751
751
  })
752
752
 
@@ -757,8 +757,8 @@ test('Should log a warning if is an async function with `done`', t => {
757
757
  try {
758
758
  fastify.addHook('onRequest', async (req, reply, done) => {})
759
759
  } catch (e) {
760
- t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
761
- t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
760
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
761
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
762
762
  }
763
763
  })
764
764
 
@@ -769,20 +769,20 @@ test('Should log a warning if is an async function with `done`', t => {
769
769
  try {
770
770
  fastify.addHook('onSend', async (req, reply, payload, done) => {})
771
771
  } catch (e) {
772
- t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
773
- t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
772
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
773
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
774
774
  }
775
775
  try {
776
776
  fastify.addHook('preSerialization', async (req, reply, payload, done) => {})
777
777
  } catch (e) {
778
- t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
779
- t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
778
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
779
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
780
780
  }
781
781
  try {
782
782
  fastify.addHook('onError', async (req, reply, payload, done) => {})
783
783
  } catch (e) {
784
- t.ok(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
785
- t.ok(e.message === 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
784
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
785
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
786
786
  }
787
787
  })
788
788
 
@@ -923,3 +923,153 @@ t.test('nested hooks to do not crash on 404', t => {
923
923
  t.equal(res.statusCode, 404)
924
924
  })
925
925
  })
926
+
927
+ test('Register an hook (preHandler) as route option should fail if mixing async and callback style', t => {
928
+ t.plan(2)
929
+ const fastify = Fastify()
930
+
931
+ try {
932
+ fastify.get(
933
+ '/',
934
+ {
935
+ preHandler: [
936
+ async (request, reply, done) => {
937
+ done()
938
+ }
939
+ ]
940
+ },
941
+ async (request, reply) => {
942
+ return { hello: 'world' }
943
+ }
944
+ )
945
+ t.fail('preHandler mixing async and callback style')
946
+ } catch (e) {
947
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
948
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
949
+ }
950
+ })
951
+
952
+ test('Register an hook (onSend) as route option should fail if mixing async and callback style', t => {
953
+ t.plan(2)
954
+ const fastify = Fastify()
955
+
956
+ try {
957
+ fastify.get(
958
+ '/',
959
+ {
960
+ onSend: [
961
+ async (request, reply, payload, done) => {
962
+ done()
963
+ }
964
+ ]
965
+ },
966
+ async (request, reply) => {
967
+ return { hello: 'world' }
968
+ }
969
+ )
970
+ t.fail('onSend mixing async and callback style')
971
+ } catch (e) {
972
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
973
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
974
+ }
975
+ })
976
+
977
+ test('Register an hook (preSerialization) as route option should fail if mixing async and callback style', t => {
978
+ t.plan(2)
979
+ const fastify = Fastify()
980
+
981
+ try {
982
+ fastify.get(
983
+ '/',
984
+ {
985
+ preSerialization: [
986
+ async (request, reply, payload, done) => {
987
+ done()
988
+ }
989
+ ]
990
+ },
991
+ async (request, reply) => {
992
+ return { hello: 'world' }
993
+ }
994
+ )
995
+ t.fail('preSerialization mixing async and callback style')
996
+ } catch (e) {
997
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
998
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
999
+ }
1000
+ })
1001
+
1002
+ test('Register an hook (onError) as route option should fail if mixing async and callback style', t => {
1003
+ t.plan(2)
1004
+ const fastify = Fastify()
1005
+
1006
+ try {
1007
+ fastify.get(
1008
+ '/',
1009
+ {
1010
+ onError: [
1011
+ async (request, reply, error, done) => {
1012
+ done()
1013
+ }
1014
+ ]
1015
+ },
1016
+ async (request, reply) => {
1017
+ return { hello: 'world' }
1018
+ }
1019
+ )
1020
+ t.fail('onError mixing async and callback style')
1021
+ } catch (e) {
1022
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
1023
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
1024
+ }
1025
+ })
1026
+
1027
+ test('Register an hook (preParsing) as route option should fail if mixing async and callback style', t => {
1028
+ t.plan(2)
1029
+ const fastify = Fastify()
1030
+
1031
+ try {
1032
+ fastify.get(
1033
+ '/',
1034
+ {
1035
+ preParsing: [
1036
+ async (request, reply, payload, done) => {
1037
+ done()
1038
+ }
1039
+ ]
1040
+ },
1041
+ async (request, reply) => {
1042
+ return { hello: 'world' }
1043
+ }
1044
+ )
1045
+ t.fail('preParsing mixing async and callback style')
1046
+ } catch (e) {
1047
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
1048
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
1049
+ }
1050
+ })
1051
+
1052
+ test('Register an hook (onRequestAbort) as route option should fail if mixing async and callback style', t => {
1053
+ t.plan(2)
1054
+ const fastify = Fastify()
1055
+
1056
+ try {
1057
+ fastify.get(
1058
+ '/',
1059
+ {
1060
+ onRequestAbort: [
1061
+ async (request, done) => {
1062
+ done()
1063
+ }
1064
+ ]
1065
+ },
1066
+ async (request, reply) => {
1067
+ return { hello: 'world' }
1068
+ }
1069
+ )
1070
+ t.fail('onRequestAbort mixing async and callback style')
1071
+ } catch (e) {
1072
+ t.equal(e.code, 'FST_ERR_HOOK_INVALID_ASYNC_HANDLER')
1073
+ t.equal(e.message, 'Async function has too many arguments. Async hooks should not use the \'done\' argument.')
1074
+ }
1075
+ })
@@ -72,7 +72,7 @@ module.exports.payloadMethod = function (method, t) {
72
72
  const result = schema.validateSync(data, yupOptions)
73
73
  return { value: result }
74
74
  } catch (e) {
75
- return { error: e }
75
+ return { error: [e] }
76
76
  }
77
77
  }
78
78
  }
@@ -286,9 +286,9 @@ module.exports.payloadMethod = function (method, t) {
286
286
  }, (err, response, body) => {
287
287
  t.error(err)
288
288
  t.equal(response.statusCode, 400)
289
- t.same(body, {
289
+ t.match(body, {
290
290
  error: 'Bad Request',
291
- message: 'hello must be a `string` type, but the final value was: `44`.',
291
+ message: /body hello must be a `string` type, but the final value was: `44`./,
292
292
  statusCode: 400,
293
293
  code: 'FST_ERR_VALIDATION'
294
294
  })
@@ -14,7 +14,8 @@ const {
14
14
  kReplySerializer,
15
15
  kReplyIsError,
16
16
  kReplySerializerDefault,
17
- kRouteContext
17
+ kRouteContext,
18
+ kPublicRouteContext
18
19
  } = require('../../lib/symbols')
19
20
  const fs = require('node:fs')
20
21
  const path = require('node:path')
@@ -35,10 +36,10 @@ const doGet = function (url) {
35
36
  }
36
37
 
37
38
  test('Once called, Reply should return an object with methods', t => {
38
- t.plan(14)
39
+ t.plan(16)
39
40
  const response = { res: 'res' }
40
- const context = {}
41
- const request = { [kRouteContext]: context }
41
+ const context = { config: { onSend: [] }, schema: {} }
42
+ const request = { [kRouteContext]: context, [kPublicRouteContext]: { config: context.config, schema: context.schema } }
42
43
  const reply = new Reply(response, request)
43
44
  t.equal(typeof reply, 'object')
44
45
  t.equal(typeof reply[kReplyIsError], 'boolean')
@@ -52,6 +53,8 @@ test('Once called, Reply should return an object with methods', t => {
52
53
  t.equal(typeof reply[kReplyHeaders], 'object')
53
54
  t.same(reply.raw, response)
54
55
  t.equal(reply[kRouteContext], context)
56
+ t.equal(reply[kPublicRouteContext].config, context.config)
57
+ t.equal(reply[kPublicRouteContext].schema, context.schema)
55
58
  t.equal(reply.request, request)
56
59
  // Aim to not bad property keys (including Symbols)
57
60
  t.notOk('undefined' in reply)
@@ -1487,6 +1490,32 @@ test('should emit deprecation warning when trying to modify the reply.sent prope
1487
1490
  })
1488
1491
  })
1489
1492
 
1493
+ test('should emit deprecation warning when trying to use the reply.context.config property', t => {
1494
+ t.plan(4)
1495
+ const fastify = Fastify()
1496
+
1497
+ const deprecationCode = 'FSTDEP019'
1498
+ warning.emitted.delete(deprecationCode)
1499
+
1500
+ process.removeAllListeners('warning')
1501
+ process.on('warning', onWarning)
1502
+ function onWarning (warning) {
1503
+ t.equal(warning.name, 'FastifyDeprecation')
1504
+ t.equal(warning.code, deprecationCode)
1505
+ }
1506
+
1507
+ fastify.get('/', (req, reply) => {
1508
+ req.log(reply.context.config)
1509
+ })
1510
+
1511
+ fastify.inject('/', (err, res) => {
1512
+ t.error(err)
1513
+ t.pass()
1514
+
1515
+ process.removeListener('warning', onWarning)
1516
+ })
1517
+ })
1518
+
1490
1519
  test('should throw error when passing falsy value to reply.sent', t => {
1491
1520
  t.plan(4)
1492
1521
  const fastify = Fastify()