fastify 5.3.3 → 5.5.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 (137) hide show
  1. package/.vscode/settings.json +15 -15
  2. package/LICENSE +1 -1
  3. package/README.md +2 -0
  4. package/SECURITY.md +158 -2
  5. package/build/build-validation.js +20 -1
  6. package/docs/Guides/Delay-Accepting-Requests.md +8 -5
  7. package/docs/Guides/Ecosystem.md +20 -5
  8. package/docs/Guides/Migration-Guide-V5.md +6 -10
  9. package/docs/Guides/Recommendations.md +1 -1
  10. package/docs/Reference/ContentTypeParser.md +1 -1
  11. package/docs/Reference/Errors.md +5 -3
  12. package/docs/Reference/Hooks.md +16 -20
  13. package/docs/Reference/Lifecycle.md +2 -2
  14. package/docs/Reference/Logging.md +3 -3
  15. package/docs/Reference/Middleware.md +1 -1
  16. package/docs/Reference/Reply.md +8 -8
  17. package/docs/Reference/Request.md +2 -2
  18. package/docs/Reference/Routes.md +7 -6
  19. package/docs/Reference/Server.md +341 -200
  20. package/docs/Reference/TypeScript.md +1 -3
  21. package/docs/Reference/Validation-and-Serialization.md +56 -4
  22. package/docs/Reference/Warnings.md +2 -1
  23. package/fastify.d.ts +4 -3
  24. package/fastify.js +47 -34
  25. package/lib/configValidator.js +196 -28
  26. package/lib/contentTypeParser.js +41 -48
  27. package/lib/error-handler.js +3 -3
  28. package/lib/errors.js +11 -0
  29. package/lib/handleRequest.js +13 -17
  30. package/lib/pluginOverride.js +3 -1
  31. package/lib/promise.js +23 -0
  32. package/lib/reply.js +24 -30
  33. package/lib/request.js +3 -10
  34. package/lib/route.js +37 -3
  35. package/lib/server.js +36 -35
  36. package/lib/symbols.js +1 -0
  37. package/lib/warnings.js +19 -1
  38. package/package.json +14 -10
  39. package/test/404s.test.js +226 -325
  40. package/test/allow-unsafe-regex.test.js +19 -48
  41. package/test/als.test.js +28 -40
  42. package/test/async-await.test.js +84 -128
  43. package/test/async_hooks.test.js +18 -37
  44. package/test/body-limit.test.js +90 -63
  45. package/test/buffer.test.js +22 -0
  46. package/test/build-certificate.js +1 -1
  47. package/test/case-insensitive.test.js +44 -65
  48. package/test/check.test.js +17 -21
  49. package/test/close-pipelining.test.js +24 -15
  50. package/test/constrained-routes.test.js +231 -0
  51. package/test/custom-http-server.test.js +7 -15
  52. package/test/custom-parser-async.test.js +17 -22
  53. package/test/custom-parser.0.test.js +267 -348
  54. package/test/custom-parser.1.test.js +141 -191
  55. package/test/custom-parser.2.test.js +34 -44
  56. package/test/custom-parser.3.test.js +56 -104
  57. package/test/custom-parser.4.test.js +106 -144
  58. package/test/custom-parser.5.test.js +56 -75
  59. package/test/custom-querystring-parser.test.js +51 -77
  60. package/test/decorator-namespace.test._js_ +3 -4
  61. package/test/decorator.test.js +76 -259
  62. package/test/delete.test.js +101 -110
  63. package/test/diagnostics-channel/404.test.js +7 -15
  64. package/test/diagnostics-channel/async-delay-request.test.js +7 -16
  65. package/test/diagnostics-channel/async-request.test.js +8 -16
  66. package/test/diagnostics-channel/error-request.test.js +7 -15
  67. package/test/diagnostics-channel/sync-delay-request.test.js +7 -16
  68. package/test/diagnostics-channel/sync-request-reply.test.js +9 -16
  69. package/test/diagnostics-channel/sync-request.test.js +9 -16
  70. package/test/fastify-instance.test.js +1 -1
  71. package/test/header-overflow.test.js +18 -29
  72. package/test/helper.js +139 -135
  73. package/test/hooks-async.test.js +259 -235
  74. package/test/hooks.test.js +951 -996
  75. package/test/http-methods/copy.test.js +14 -19
  76. package/test/http-methods/get.test.js +131 -143
  77. package/test/http-methods/head.test.js +53 -84
  78. package/test/http-methods/lock.test.js +31 -31
  79. package/test/http-methods/mkcalendar.test.js +45 -72
  80. package/test/http-methods/mkcol.test.js +5 -9
  81. package/test/http-methods/move.test.js +6 -10
  82. package/test/http-methods/propfind.test.js +34 -44
  83. package/test/http-methods/proppatch.test.js +23 -29
  84. package/test/http-methods/report.test.js +44 -69
  85. package/test/http-methods/search.test.js +67 -82
  86. package/test/http-methods/unlock.test.js +5 -9
  87. package/test/http2/closing.test.js +38 -20
  88. package/test/http2/secure-with-fallback.test.js +31 -28
  89. package/test/https/custom-https-server.test.js +9 -13
  90. package/test/https/https.test.js +56 -53
  91. package/test/input-validation.js +139 -150
  92. package/test/internals/errors.test.js +50 -1
  93. package/test/internals/handle-request.test.js +72 -65
  94. package/test/internals/promise.test.js +63 -0
  95. package/test/internals/reply.test.js +277 -496
  96. package/test/issue-4959.test.js +12 -3
  97. package/test/listen.4.test.js +31 -43
  98. package/test/nullable-validation.test.js +33 -46
  99. package/test/output-validation.test.js +24 -26
  100. package/test/plugin.1.test.js +40 -68
  101. package/test/plugin.2.test.js +108 -120
  102. package/test/plugin.3.test.js +50 -72
  103. package/test/plugin.4.test.js +124 -119
  104. package/test/promises.test.js +42 -63
  105. package/test/proto-poisoning.test.js +78 -97
  106. package/test/register.test.js +8 -18
  107. package/test/request-error.test.js +57 -146
  108. package/test/request-id.test.js +30 -49
  109. package/test/route-hooks.test.js +117 -101
  110. package/test/route-prefix.test.js +194 -133
  111. package/test/route-shorthand.test.js +9 -27
  112. package/test/route.1.test.js +74 -131
  113. package/test/route.8.test.js +9 -17
  114. package/test/router-options.test.js +450 -0
  115. package/test/schema-serialization.test.js +177 -154
  116. package/test/schema-special-usage.test.js +165 -132
  117. package/test/schema-validation.test.js +254 -218
  118. package/test/server.test.js +143 -5
  119. package/test/set-error-handler.test.js +58 -1
  120. package/test/skip-reply-send.test.js +64 -69
  121. package/test/stream.1.test.js +33 -50
  122. package/test/stream.4.test.js +18 -28
  123. package/test/stream.5.test.js +11 -19
  124. package/test/trust-proxy.test.js +32 -58
  125. package/test/types/errors.test-d.ts +13 -1
  126. package/test/types/fastify.test-d.ts +3 -0
  127. package/test/types/request.test-d.ts +1 -0
  128. package/test/types/type-provider.test-d.ts +55 -0
  129. package/test/url-rewriting.test.js +45 -62
  130. package/test/use-semicolon-delimiter.test.js +117 -59
  131. package/test/versioned-routes.test.js +39 -56
  132. package/types/errors.d.ts +11 -1
  133. package/types/hooks.d.ts +1 -1
  134. package/types/instance.d.ts +1 -1
  135. package/types/reply.d.ts +2 -2
  136. package/types/request.d.ts +1 -0
  137. package/.taprc +0 -7
@@ -905,3 +905,234 @@ test('Allow regex constraints in routes', async t => {
905
905
  t.assert.strictEqual(res.statusCode, 404)
906
906
  }
907
907
  })
908
+
909
+ test('Should allow registering custom rotuerOptions constrained routes', async t => {
910
+ t.plan(5)
911
+
912
+ const constraint = {
913
+ name: 'secret',
914
+ storage: function () {
915
+ const secrets = {}
916
+ return {
917
+ get: (secret) => { return secrets[secret] || null },
918
+ set: (secret, store) => { secrets[secret] = store }
919
+ }
920
+ },
921
+ deriveConstraint: (req, ctx) => {
922
+ return req.headers['x-secret']
923
+ },
924
+ validate () { return true }
925
+ }
926
+
927
+ const fastify = Fastify({ routerOptions: { constraints: { secret: constraint } } })
928
+
929
+ fastify.route({
930
+ method: 'GET',
931
+ url: '/',
932
+ constraints: { secret: 'alpha' },
933
+ handler: (req, reply) => {
934
+ reply.send({ hello: 'from alpha' })
935
+ }
936
+ })
937
+
938
+ fastify.route({
939
+ method: 'GET',
940
+ url: '/',
941
+ constraints: { secret: 'beta' },
942
+ handler: (req, reply) => {
943
+ reply.send({ hello: 'from beta' })
944
+ }
945
+ })
946
+
947
+ {
948
+ const res = await fastify.inject({
949
+ method: 'GET',
950
+ url: '/',
951
+ headers: {
952
+ 'X-Secret': 'alpha'
953
+ }
954
+ })
955
+ t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from alpha' })
956
+ t.assert.strictEqual(res.statusCode, 200)
957
+ }
958
+
959
+ {
960
+ const res = await fastify.inject({
961
+ method: 'GET',
962
+ url: '/',
963
+ headers: {
964
+ 'X-Secret': 'beta'
965
+ }
966
+ })
967
+ t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'from beta' })
968
+ t.assert.strictEqual(res.statusCode, 200)
969
+ }
970
+
971
+ {
972
+ const res = await fastify.inject({
973
+ method: 'GET',
974
+ url: '/',
975
+ headers: {
976
+ 'X-Secret': 'gamma'
977
+ }
978
+ })
979
+ t.assert.strictEqual(res.statusCode, 404)
980
+ }
981
+ })
982
+
983
+ test('Custom rotuerOptions constrained routes registered also for HEAD method generated by fastify', (t, done) => {
984
+ t.plan(3)
985
+
986
+ const constraint = {
987
+ name: 'secret',
988
+ storage: function () {
989
+ const secrets = {}
990
+ return {
991
+ get: (secret) => { return secrets[secret] || null },
992
+ set: (secret, store) => { secrets[secret] = store }
993
+ }
994
+ },
995
+ deriveConstraint: (req, ctx) => {
996
+ return req.headers['x-secret']
997
+ },
998
+ validate () { return true }
999
+ }
1000
+
1001
+ const fastify = Fastify({ routerOptions: { constraints: { secret: constraint } } })
1002
+
1003
+ fastify.route({
1004
+ method: 'GET',
1005
+ url: '/',
1006
+ constraints: { secret: 'mySecret' },
1007
+ handler: (req, reply) => {
1008
+ reply.send('from mySecret - my length is 31')
1009
+ }
1010
+ })
1011
+
1012
+ fastify.inject({
1013
+ method: 'HEAD',
1014
+ url: '/',
1015
+ headers: {
1016
+ 'X-Secret': 'mySecret'
1017
+ }
1018
+ }, (err, res) => {
1019
+ t.assert.ifError(err)
1020
+ t.assert.deepStrictEqual(res.headers['content-length'], '31')
1021
+ t.assert.strictEqual(res.statusCode, 200)
1022
+ done()
1023
+ })
1024
+ })
1025
+
1026
+ test('allow async rotuerOptions constraints', async (t) => {
1027
+ t.plan(5)
1028
+
1029
+ const constraint = {
1030
+ name: 'secret',
1031
+ storage: function () {
1032
+ const secrets = {}
1033
+ return {
1034
+ get: (secret) => { return secrets[secret] || null },
1035
+ set: (secret, store) => { secrets[secret] = store }
1036
+ }
1037
+ },
1038
+ deriveConstraint: (req, ctx, done) => {
1039
+ done(null, req.headers['x-secret'])
1040
+ },
1041
+ validate () { return true }
1042
+ }
1043
+
1044
+ const fastify = Fastify({ routerOptions: { constraints: { secret: constraint } } })
1045
+
1046
+ fastify.route({
1047
+ method: 'GET',
1048
+ url: '/',
1049
+ constraints: { secret: 'alpha' },
1050
+ handler: (req, reply) => {
1051
+ reply.send({ hello: 'from alpha' })
1052
+ }
1053
+ })
1054
+
1055
+ fastify.route({
1056
+ method: 'GET',
1057
+ url: '/',
1058
+ constraints: { secret: 'beta' },
1059
+ handler: (req, reply) => {
1060
+ reply.send({ hello: 'from beta' })
1061
+ }
1062
+ })
1063
+
1064
+ {
1065
+ const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'alpha' } })
1066
+ t.assert.deepStrictEqual(JSON.parse(payload), { hello: 'from alpha' })
1067
+ t.assert.strictEqual(statusCode, 200)
1068
+ }
1069
+ {
1070
+ const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'beta' } })
1071
+ t.assert.deepStrictEqual(JSON.parse(payload), { hello: 'from beta' })
1072
+ t.assert.strictEqual(statusCode, 200)
1073
+ }
1074
+ {
1075
+ const { statusCode } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'gamma' } })
1076
+ t.assert.strictEqual(statusCode, 404)
1077
+ }
1078
+ })
1079
+
1080
+ test('error in async rotuerOptions constraints', async (t) => {
1081
+ t.plan(8)
1082
+
1083
+ const constraint = {
1084
+ name: 'secret',
1085
+ storage: function () {
1086
+ const secrets = {}
1087
+ return {
1088
+ get: (secret) => { return secrets[secret] || null },
1089
+ set: (secret, store) => { secrets[secret] = store }
1090
+ }
1091
+ },
1092
+ deriveConstraint: (req, ctx, done) => {
1093
+ done(Error('kaboom'))
1094
+ },
1095
+ validate () { return true }
1096
+ }
1097
+
1098
+ const fastify = Fastify({ routerOptions: { constraints: { secret: constraint } } })
1099
+
1100
+ fastify.route({
1101
+ method: 'GET',
1102
+ url: '/',
1103
+ constraints: { secret: 'alpha' },
1104
+ handler: (req, reply) => {
1105
+ reply.send({ hello: 'from alpha' })
1106
+ }
1107
+ })
1108
+
1109
+ fastify.route({
1110
+ method: 'GET',
1111
+ url: '/',
1112
+ constraints: { secret: 'beta' },
1113
+ handler: (req, reply) => {
1114
+ reply.send({ hello: 'from beta' })
1115
+ }
1116
+ })
1117
+
1118
+ {
1119
+ const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'alpha' } })
1120
+ t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 })
1121
+ t.assert.strictEqual(statusCode, 500)
1122
+ }
1123
+ {
1124
+ const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'beta' } })
1125
+ t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 })
1126
+ t.assert.strictEqual(statusCode, 500)
1127
+ }
1128
+ {
1129
+ const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/', headers: { 'X-Secret': 'gamma' } })
1130
+ t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 })
1131
+ t.assert.strictEqual(statusCode, 500)
1132
+ }
1133
+ {
1134
+ const { statusCode, payload } = await fastify.inject({ method: 'GET', path: '/' })
1135
+ t.assert.deepStrictEqual(JSON.parse(payload), { error: 'Internal Server Error', message: 'Unexpected error from async constraint', statusCode: 500 })
1136
+ t.assert.strictEqual(statusCode, 500)
1137
+ }
1138
+ })
@@ -3,7 +3,6 @@
3
3
  const { test } = require('node:test')
4
4
  const http = require('node:http')
5
5
  const dns = require('node:dns').promises
6
- const sget = require('simple-get').concat
7
6
  const Fastify = require('..')
8
7
  const { FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE } = require('../lib/errors')
9
8
 
@@ -11,7 +10,7 @@ async function setup () {
11
10
  const localAddresses = await dns.lookup('localhost', { all: true })
12
11
 
13
12
  test('Should support a custom http server', { skip: localAddresses.length < 1 }, async t => {
14
- t.plan(4)
13
+ t.plan(5)
15
14
 
16
15
  const fastify = Fastify({
17
16
  serverFactory: (handler, opts) => {
@@ -34,20 +33,13 @@ async function setup () {
34
33
 
35
34
  await fastify.listen({ port: 0 })
36
35
 
37
- await new Promise((resolve, reject) => {
38
- sget({
39
- method: 'GET',
40
- url: 'http://localhost:' + fastify.server.address().port,
41
- rejectUnauthorized: false
42
- }, (err, response, body) => {
43
- if (err) {
44
- return reject(err)
45
- }
46
- t.assert.strictEqual(response.statusCode, 200)
47
- t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
48
- resolve()
49
- })
36
+ const response = await fetch('http://localhost:' + fastify.server.address().port, {
37
+ method: 'GET'
50
38
  })
39
+ t.assert.ok(response.ok)
40
+ t.assert.strictEqual(response.status, 200)
41
+ const body = await response.text()
42
+ t.assert.deepStrictEqual(JSON.parse(body), { hello: 'world' })
51
43
  })
52
44
 
53
45
  test('Should not allow forceCloseConnection=idle if the server does not support closeIdleConnections', t => {
@@ -1,7 +1,6 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('node:test')
4
- const sget = require('simple-get').concat
5
4
  const Fastify = require('../fastify')
6
5
 
7
6
  process.removeAllListeners('warning')
@@ -24,41 +23,37 @@ test('contentTypeParser should add a custom async parser', async t => {
24
23
  })
25
24
 
26
25
  t.after(() => fastify.close())
27
- await fastify.listen({ port: 0 })
26
+ const fastifyServer = await fastify.listen({ port: 0 })
28
27
 
29
- await t.test('in POST', (t, done) => {
28
+ await t.test('in POST', async t => {
30
29
  t.plan(3)
31
30
 
32
- sget({
31
+ const result = await fetch(fastifyServer, {
33
32
  method: 'POST',
34
- url: 'http://localhost:' + fastify.server.address().port,
35
- body: '{"hello":"world"}',
36
33
  headers: {
37
34
  'Content-Type': 'application/jsoff'
38
- }
39
- }, (err, response, body) => {
40
- t.assert.ifError(err)
41
- t.assert.strictEqual(response.statusCode, 200)
42
- t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' }))
43
- done()
35
+ },
36
+ body: '{"hello":"world"}'
44
37
  })
38
+
39
+ t.assert.ok(result.ok)
40
+ t.assert.strictEqual(result.status, 200)
41
+ t.assert.deepStrictEqual(await result.json(), { hello: 'world' })
45
42
  })
46
43
 
47
- await t.test('in OPTIONS', (t, done) => {
44
+ await t.test('in OPTIONS', async t => {
48
45
  t.plan(3)
49
46
 
50
- sget({
47
+ const result = await fetch(fastifyServer, {
51
48
  method: 'OPTIONS',
52
- url: 'http://localhost:' + fastify.server.address().port,
53
- body: '{"hello":"world"}',
54
49
  headers: {
55
50
  'Content-Type': 'application/jsoff'
56
- }
57
- }, (err, response, body) => {
58
- t.assert.ifError(err)
59
- t.assert.strictEqual(response.statusCode, 200)
60
- t.assert.deepStrictEqual(body.toString(), JSON.stringify({ hello: 'world' }))
61
- done()
51
+ },
52
+ body: '{"hello":"world"}'
62
53
  })
54
+
55
+ t.assert.ok(result.ok)
56
+ t.assert.strictEqual(result.status, 200)
57
+ t.assert.deepStrictEqual(await result.json(), { hello: 'world' })
63
58
  })
64
59
  })