fastify 5.0.0 → 5.2.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 (201) hide show
  1. package/.borp.yaml +3 -0
  2. package/.vscode/settings.json +22 -0
  3. package/README.md +12 -7
  4. package/docs/Guides/Database.md +15 -15
  5. package/docs/Guides/Detecting-When-Clients-Abort.md +28 -28
  6. package/docs/Guides/Ecosystem.md +14 -15
  7. package/docs/Guides/Index.md +1 -1
  8. package/docs/Guides/Migration-Guide-V4.md +11 -11
  9. package/docs/Guides/Migration-Guide-V5.md +133 -9
  10. package/docs/Guides/Plugins-Guide.md +1 -1
  11. package/docs/Guides/Prototype-Poisoning.md +3 -3
  12. package/docs/Guides/Recommendations.md +9 -9
  13. package/docs/Guides/Serverless.md +5 -5
  14. package/docs/Guides/Testing.md +58 -57
  15. package/docs/Guides/Write-Plugin.md +2 -2
  16. package/docs/Guides/Write-Type-Provider.md +3 -3
  17. package/docs/Reference/ContentTypeParser.md +4 -4
  18. package/docs/Reference/Decorators.md +2 -2
  19. package/docs/Reference/Errors.md +3 -3
  20. package/docs/Reference/Hooks.md +7 -7
  21. package/docs/Reference/LTS.md +8 -0
  22. package/docs/Reference/Logging.md +5 -4
  23. package/docs/Reference/Reply.md +55 -58
  24. package/docs/Reference/Request.md +49 -42
  25. package/docs/Reference/Routes.md +16 -13
  26. package/docs/Reference/Server.md +32 -28
  27. package/docs/Reference/TypeScript.md +9 -9
  28. package/docs/Reference/Validation-and-Serialization.md +5 -5
  29. package/examples/typescript-server.ts +1 -1
  30. package/fastify.d.ts +14 -5
  31. package/fastify.js +8 -6
  32. package/lib/contentTypeParser.js +9 -7
  33. package/lib/context.js +1 -2
  34. package/lib/error-handler.js +9 -9
  35. package/lib/errors.js +1 -1
  36. package/lib/fourOhFour.js +1 -1
  37. package/lib/hooks.js +4 -1
  38. package/lib/{logger.js → logger-factory.js} +70 -122
  39. package/lib/logger-pino.js +68 -0
  40. package/lib/pluginOverride.js +1 -1
  41. package/lib/pluginUtils.js +2 -2
  42. package/lib/reply.js +4 -5
  43. package/lib/request.js +16 -9
  44. package/lib/route.js +23 -22
  45. package/lib/validation.js +2 -2
  46. package/package.json +13 -15
  47. package/test/404s.test.js +675 -629
  48. package/test/500s.test.js +72 -63
  49. package/test/{allowUnsafeRegex.test.js → allow-unsafe-regex.test.js} +30 -26
  50. package/test/als.test.js +48 -45
  51. package/test/async-await.test.js +148 -134
  52. package/test/async-dispose.test.js +4 -5
  53. package/test/async_hooks.test.js +30 -28
  54. package/test/{bodyLimit.test.js → body-limit.test.js} +61 -58
  55. package/test/buffer.test.js +9 -10
  56. package/test/build/error-serializer.test.js +3 -4
  57. package/test/build/version.test.js +2 -3
  58. package/test/build-certificate.js +1 -1
  59. package/test/bundler/README.md +5 -5
  60. package/test/bundler/esbuild/bundler-test.js +10 -9
  61. package/test/bundler/webpack/bundler-test.js +10 -9
  62. package/test/case-insensitive.test.js +31 -28
  63. package/test/chainable.test.js +4 -5
  64. package/test/check.test.js +8 -10
  65. package/test/{childLoggerFactory.test.js → child-logger-factory.test.js} +56 -19
  66. package/test/client-timeout.test.js +5 -5
  67. package/test/close-pipelining.test.js +6 -8
  68. package/test/conditional-pino.test.js +47 -0
  69. package/test/{connectionTimeout.test.js → connection-timeout.test.js} +10 -11
  70. package/test/constrained-routes.test.js +243 -236
  71. package/test/content-length.test.js +53 -68
  72. package/test/content-parser.test.js +186 -158
  73. package/test/content-type.test.js +8 -9
  74. package/test/context-config.test.js +44 -54
  75. package/test/custom-http-server.test.js +16 -20
  76. package/test/custom-parser.5.test.js +32 -32
  77. package/test/diagnostics-channel/404.test.js +15 -15
  78. package/test/diagnostics-channel/async-delay-request.test.js +25 -25
  79. package/test/diagnostics-channel/async-request.test.js +24 -24
  80. package/test/diagnostics-channel/error-before-handler.test.js +4 -5
  81. package/test/diagnostics-channel/error-request.test.js +19 -19
  82. package/test/diagnostics-channel/error-status.test.js +8 -8
  83. package/test/diagnostics-channel/init.test.js +6 -7
  84. package/test/diagnostics-channel/sync-delay-request.test.js +16 -16
  85. package/test/diagnostics-channel/sync-request-reply.test.js +16 -16
  86. package/test/diagnostics-channel/sync-request.test.js +19 -19
  87. package/test/encapsulated-child-logger-factory.test.js +8 -8
  88. package/test/encapsulated-error-handler.test.js +20 -20
  89. package/test/esm/errorCodes.test.mjs +5 -5
  90. package/test/esm/esm.test.mjs +3 -3
  91. package/test/esm/named-exports.mjs +3 -3
  92. package/test/esm/other.mjs +2 -2
  93. package/test/fastify-instance.test.js +33 -34
  94. package/test/{findRoute.test.js → find-route.test.js} +11 -10
  95. package/test/fluent-schema.test.js +33 -36
  96. package/test/handler-context.test.js +11 -11
  97. package/test/has-route.test.js +12 -15
  98. package/test/header-overflow.test.js +13 -12
  99. package/test/hooks.on-ready.test.js +2 -2
  100. package/test/hooks.test.js +25 -25
  101. package/test/http-methods/copy.test.js +22 -24
  102. package/test/http-methods/custom-http-methods.test.js +24 -21
  103. package/test/http-methods/get.test.js +97 -84
  104. package/test/http-methods/head.test.js +63 -57
  105. package/test/http-methods/lock.test.js +21 -20
  106. package/test/http-methods/mkcalendar.test.js +31 -27
  107. package/test/http-methods/mkcol.test.js +10 -10
  108. package/test/http-methods/move.test.js +11 -11
  109. package/test/http-methods/propfind.test.js +32 -27
  110. package/test/http-methods/proppatch.test.js +21 -19
  111. package/test/http-methods/report.test.js +32 -27
  112. package/test/http-methods/search.test.js +52 -47
  113. package/test/http-methods/trace.test.js +3 -4
  114. package/test/http-methods/unlock.test.js +10 -10
  115. package/test/http2/closing.test.js +50 -58
  116. package/test/http2/constraint.test.js +47 -50
  117. package/test/http2/head.test.js +18 -19
  118. package/test/http2/missing-http2-module.test.js +4 -5
  119. package/test/http2/plain.test.js +31 -31
  120. package/test/http2/secure-with-fallback.test.js +61 -61
  121. package/test/http2/secure.test.js +28 -31
  122. package/test/http2/unknown-http-method.test.js +13 -14
  123. package/test/https/custom-https-server.test.js +6 -7
  124. package/test/https/https.test.js +78 -78
  125. package/test/imports.test.js +5 -6
  126. package/test/internals/all.test.js +8 -11
  127. package/test/internals/{contentTypeParser.test.js → content-type-parser.test.js} +5 -6
  128. package/test/internals/context.test.js +9 -11
  129. package/test/internals/decorator.test.js +20 -21
  130. package/test/internals/errors.test.js +427 -427
  131. package/test/internals/{handleRequest.test.js → handle-request.test.js} +53 -42
  132. package/test/internals/{hookRunner.test.js → hook-runner.test.js} +99 -100
  133. package/test/internals/hooks.test.js +31 -35
  134. package/test/internals/{initialConfig.test.js → initial-config.test.js} +92 -80
  135. package/test/internals/logger.test.js +28 -28
  136. package/test/internals/plugin.test.js +17 -18
  137. package/test/internals/reply-serialize.test.js +106 -106
  138. package/test/internals/reply.test.js +620 -585
  139. package/test/internals/{reqIdGenFactory.test.js → req-id-gen-factory.test.js} +31 -31
  140. package/test/internals/request-validate.test.js +218 -221
  141. package/test/internals/request.test.js +225 -107
  142. package/test/internals/server.test.js +15 -12
  143. package/test/internals/validation.test.js +35 -36
  144. package/test/{keepAliveTimeout.test.js → keep-alive-timeout.test.js} +9 -10
  145. package/test/listen.5.test.js +9 -9
  146. package/test/{maxRequestsPerSocket.test.js → max-requests-per-socket.test.js} +30 -30
  147. package/test/middleware.test.js +4 -5
  148. package/test/noop-set.test.js +5 -5
  149. package/test/post-empty-body.test.js +18 -11
  150. package/test/pretty-print.test.js +59 -49
  151. package/test/proto-poisoning.test.js +42 -37
  152. package/test/reply-code.test.js +34 -32
  153. package/test/{reply-earlyHints.test.js → reply-early-hints.test.js} +21 -19
  154. package/test/request-error.test.js +122 -0
  155. package/test/request-header-host.test.js +339 -0
  156. package/test/request-id.test.js +31 -25
  157. package/test/{requestTimeout.test.js → request-timeout.test.js} +11 -11
  158. package/test/route.1.test.js +79 -72
  159. package/test/route.2.test.js +17 -16
  160. package/test/route.3.test.js +32 -27
  161. package/test/route.4.test.js +21 -25
  162. package/test/route.5.test.js +45 -64
  163. package/test/route.6.test.js +70 -89
  164. package/test/route.7.test.js +61 -65
  165. package/test/route.8.test.js +80 -18
  166. package/test/router-options.test.js +80 -77
  167. package/test/same-shape.test.js +5 -5
  168. package/test/schema-examples.test.js +72 -38
  169. package/test/serialize-response.test.js +9 -10
  170. package/test/server.test.js +75 -78
  171. package/test/set-error-handler.test.js +2 -3
  172. package/test/stream-serializers.test.js +10 -7
  173. package/test/sync-routes.test.js +18 -18
  174. package/test/test-reporter.mjs +68 -0
  175. package/test/trust-proxy.test.js +51 -45
  176. package/test/type-provider.test.js +8 -6
  177. package/test/types/content-type-parser.test-d.ts +1 -1
  178. package/test/types/fastify.test-d.ts +16 -4
  179. package/test/types/hooks.test-d.ts +2 -1
  180. package/test/types/instance.test-d.ts +13 -13
  181. package/test/types/logger.test-d.ts +2 -2
  182. package/test/types/plugin.test-d.ts +17 -9
  183. package/test/types/register.test-d.ts +22 -6
  184. package/test/types/reply.test-d.ts +1 -1
  185. package/test/types/route.test-d.ts +34 -4
  186. package/test/types/serverFactory.test-d.ts +1 -1
  187. package/test/types/type-provider.test-d.ts +1 -1
  188. package/test/url-rewriting.test.js +35 -38
  189. package/test/{useSemicolonDelimiter.test.js → use-semicolon-delimiter.test.js} +30 -30
  190. package/test/validation-error-handling.test.js +259 -285
  191. package/test/versioned-routes.test.js +126 -113
  192. package/test/web-api.test.js +48 -37
  193. package/test/{wrapThenable.test.js → wrap-thenable.test.js} +10 -9
  194. package/types/hooks.d.ts +2 -1
  195. package/types/instance.d.ts +9 -2
  196. package/types/register.d.ts +12 -3
  197. package/types/reply.d.ts +1 -1
  198. package/types/request.d.ts +2 -6
  199. package/types/serverFactory.d.ts +3 -3
  200. package/types/utils.d.ts +13 -5
  201. package/test/types/import.js +0 -2
@@ -1,88 +1,90 @@
1
1
  'use strict'
2
2
 
3
- /**
4
- * Code imported from `pino-http`
5
- * Repo: https://github.com/pinojs/pino-http
6
- * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
7
- */
8
-
9
- const nullLogger = require('abstract-logging')
10
- const pino = require('pino')
11
- const { serializersSym } = pino.symbols
12
3
  const {
13
- FST_ERR_LOG_INVALID_DESTINATION,
14
- FST_ERR_LOG_INVALID_LOGGER,
15
- FST_ERR_LOG_INVALID_LOGGER_INSTANCE,
4
+ FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED,
16
5
  FST_ERR_LOG_INVALID_LOGGER_CONFIG,
17
- FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED
6
+ FST_ERR_LOG_INVALID_LOGGER_INSTANCE,
7
+ FST_ERR_LOG_INVALID_LOGGER
18
8
  } = require('./errors')
19
9
 
20
- function createPinoLogger (opts) {
21
- if (opts.stream && opts.file) {
22
- throw new FST_ERR_LOG_INVALID_DESTINATION()
23
- } else if (opts.file) {
24
- // we do not have stream
25
- opts.stream = pino.destination(opts.file)
26
- delete opts.file
10
+ /**
11
+ * Utility for creating a child logger with the appropriate bindings, logger factory
12
+ * and validation.
13
+ * @param {object} context
14
+ * @param {import('../fastify').FastifyBaseLogger} logger
15
+ * @param {import('../fastify').RawRequestDefaultExpression<any>} req
16
+ * @param {string} reqId
17
+ * @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
18
+ *
19
+ * @returns {object} New logger instance, inheriting all parent bindings,
20
+ * with child bindings added.
21
+ */
22
+ function createChildLogger (context, logger, req, reqId, loggerOpts) {
23
+ const loggerBindings = {
24
+ [context.requestIdLogLabel]: reqId
27
25
  }
26
+ const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
28
27
 
29
- const prevLogger = opts.logger
30
- const prevGenReqId = opts.genReqId
31
- let logger = null
32
-
33
- if (prevLogger) {
34
- opts.logger = undefined
35
- opts.genReqId = undefined
36
- // we need to tap into pino internals because in v5 it supports
37
- // adding serializers in child loggers
38
- if (prevLogger[serializersSym]) {
39
- opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym])
40
- }
41
- logger = prevLogger.child({}, opts)
42
- opts.logger = prevLogger
43
- opts.genReqId = prevGenReqId
44
- } else {
45
- logger = pino(opts, opts.stream)
28
+ // Optimization: bypass validation if the factory is our own default factory
29
+ if (context.childLoggerFactory !== defaultChildLoggerFactory) {
30
+ validateLogger(child, true) // throw if the child is not a valid logger
46
31
  }
47
32
 
48
- return logger
33
+ return child
49
34
  }
50
35
 
51
- const serializers = {
52
- req: function asReqValue (req) {
53
- return {
54
- method: req.method,
55
- url: req.url,
56
- version: req.headers && req.headers['accept-version'],
57
- host: req.host,
58
- remoteAddress: req.ip,
59
- remotePort: req.socket ? req.socket.remotePort : undefined
60
- }
61
- },
62
- err: pino.stdSerializers.err,
63
- res: function asResValue (reply) {
64
- return {
65
- statusCode: reply.statusCode
66
- }
67
- }
36
+ /** Default factory to create child logger instance
37
+ *
38
+ * @param {import('../fastify.js').FastifyBaseLogger} logger
39
+ * @param {import('../types/logger.js').Bindings} bindings
40
+ * @param {import('../types/logger.js').ChildLoggerOptions} opts
41
+ *
42
+ * @returns {import('../types/logger.js').FastifyBaseLogger}
43
+ */
44
+ function defaultChildLoggerFactory (logger, bindings, opts) {
45
+ return logger.child(bindings, opts)
68
46
  }
69
47
 
70
- function now () {
71
- const ts = process.hrtime()
72
- return (ts[0] * 1e3) + (ts[1] / 1e6)
48
+ /**
49
+ * Determines if a provided logger object meets the requirements
50
+ * of a Fastify compatible logger.
51
+ *
52
+ * @param {object} logger Object to validate.
53
+ * @param {boolean?} strict `true` if the object must be a logger (always throw if any methods missing)
54
+ *
55
+ * @returns {boolean} `true` when the logger meets the requirements.
56
+ *
57
+ * @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is
58
+ * missing required methods.
59
+ */
60
+ function validateLogger (logger, strict) {
61
+ const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
62
+ const missingMethods = logger
63
+ ? methods.filter(method => !logger[method] || typeof logger[method] !== 'function')
64
+ : methods
65
+
66
+ if (!missingMethods.length) {
67
+ return true
68
+ } else if ((missingMethods.length === methods.length) && !strict) {
69
+ return false
70
+ } else {
71
+ throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(','))
72
+ }
73
73
  }
74
74
 
75
75
  function createLogger (options) {
76
- // if no logger is provided, then create a default logger
76
+ if (options.logger && options.loggerInstance) {
77
+ throw new FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED()
78
+ }
79
+
77
80
  if (!options.loggerInstance && !options.logger) {
81
+ const nullLogger = require('abstract-logging')
78
82
  const logger = nullLogger
79
83
  logger.child = () => logger
80
84
  return { logger, hasLogger: false }
81
85
  }
82
86
 
83
- if (options.logger && options.loggerInstance) {
84
- throw new FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED()
85
- }
87
+ const { createPinoLogger, serializers } = require('./logger-pino.js')
86
88
 
87
89
  // check if the logger instance has all required properties
88
90
  if (validateLogger(options.loggerInstance)) {
@@ -120,69 +122,15 @@ function createLogger (options) {
120
122
  return { logger, hasLogger: true }
121
123
  }
122
124
 
123
- /**
124
- * Determines if a provided logger object meets the requirements
125
- * of a Fastify compatible logger.
126
- *
127
- * @param {object} logger Object to validate.
128
- * @param {boolean?} strict `true` if the object must be a logger (always throw if any methods missing)
129
- *
130
- * @returns {boolean} `true` when the logger meets the requirements.
131
- *
132
- * @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is
133
- * missing required methods.
134
- */
135
- function validateLogger (logger, strict) {
136
- const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
137
- const missingMethods = logger
138
- ? methods.filter(method => !logger[method] || typeof logger[method] !== 'function')
139
- : methods
140
-
141
- if (!missingMethods.length) {
142
- return true
143
- } else if ((missingMethods.length === methods.length) && !strict) {
144
- return false
145
- } else {
146
- throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(','))
147
- }
148
- }
149
-
150
- /**
151
- * Utility for creating a child logger with the appropriate bindings, logger factory
152
- * and validation.
153
- * @param {object} context
154
- * @param {import('../fastify').FastifyBaseLogger} logger
155
- * @param {import('../fastify').RawRequestDefaultExpression<any>} req
156
- * @param {string} reqId
157
- * @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
158
- */
159
- function createChildLogger (context, logger, req, reqId, loggerOpts) {
160
- const loggerBindings = {
161
- [context.requestIdLogLabel]: reqId
162
- }
163
- const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
164
-
165
- // Optimization: bypass validation if the factory is our own default factory
166
- if (context.childLoggerFactory !== defaultChildLoggerFactory) {
167
- validateLogger(child, true) // throw if the child is not a valid logger
168
- }
169
-
170
- return child
171
- }
172
-
173
- /**
174
- * @param {import('../fastify.js').FastifyBaseLogger} logger
175
- * @param {import('../types/logger.js').Bindings} bindings
176
- * @param {import('../types/logger.js').ChildLoggerOptions} opts
177
- */
178
- function defaultChildLoggerFactory (logger, bindings, opts) {
179
- return logger.child(bindings, opts)
125
+ function now () {
126
+ const ts = process.hrtime()
127
+ return (ts[0] * 1e3) + (ts[1] / 1e6)
180
128
  }
181
129
 
182
130
  module.exports = {
183
- createLogger,
184
131
  createChildLogger,
185
132
  defaultChildLoggerFactory,
186
- serializers,
187
- now
133
+ createLogger,
134
+ validateLogger,
135
+ now,
188
136
  }
@@ -0,0 +1,68 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Code imported from `pino-http`
5
+ * Repo: https://github.com/pinojs/pino-http
6
+ * License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
7
+ */
8
+
9
+ const pino = require('pino')
10
+ const { serializersSym } = pino.symbols
11
+ const {
12
+ FST_ERR_LOG_INVALID_DESTINATION,
13
+ } = require('./errors')
14
+
15
+ function createPinoLogger (opts) {
16
+ if (opts.stream && opts.file) {
17
+ throw new FST_ERR_LOG_INVALID_DESTINATION()
18
+ } else if (opts.file) {
19
+ // we do not have stream
20
+ opts.stream = pino.destination(opts.file)
21
+ delete opts.file
22
+ }
23
+
24
+ const prevLogger = opts.logger
25
+ const prevGenReqId = opts.genReqId
26
+ let logger = null
27
+
28
+ if (prevLogger) {
29
+ opts.logger = undefined
30
+ opts.genReqId = undefined
31
+ // we need to tap into pino internals because in v5 it supports
32
+ // adding serializers in child loggers
33
+ if (prevLogger[serializersSym]) {
34
+ opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym])
35
+ }
36
+ logger = prevLogger.child({}, opts)
37
+ opts.logger = prevLogger
38
+ opts.genReqId = prevGenReqId
39
+ } else {
40
+ logger = pino(opts, opts.stream)
41
+ }
42
+
43
+ return logger
44
+ }
45
+
46
+ const serializers = {
47
+ req: function asReqValue (req) {
48
+ return {
49
+ method: req.method,
50
+ url: req.url,
51
+ version: req.headers && req.headers['accept-version'],
52
+ host: req.host,
53
+ remoteAddress: req.ip,
54
+ remotePort: req.socket ? req.socket.remotePort : undefined
55
+ }
56
+ },
57
+ err: pino.stdSerializers.err,
58
+ res: function asResValue (reply) {
59
+ return {
60
+ statusCode: reply.statusCode
61
+ }
62
+ }
63
+ }
64
+
65
+ module.exports = {
66
+ serializers,
67
+ createPinoLogger,
68
+ }
@@ -66,7 +66,7 @@ module.exports = function override (old, fn, opts) {
66
66
  instance[kFourOhFour].arrange404(instance)
67
67
  }
68
68
 
69
- for (const hook of instance[kHooks].onRegister) hook.call(this, instance, opts)
69
+ for (const hook of instance[kHooks].onRegister) hook.call(old, instance, opts)
70
70
 
71
71
  return instance
72
72
  }
@@ -48,7 +48,7 @@ function getPluginName (func) {
48
48
 
49
49
  function getFuncPreview (func) {
50
50
  // takes the first two lines of the function if nothing else works
51
- return func.toString().split('\n').slice(0, 2).map(s => s.trim()).join(' -- ')
51
+ return func.toString().split('\n', 2).map(s => s.trim()).join(' -- ')
52
52
  }
53
53
 
54
54
  function getDisplayName (fn) {
@@ -110,7 +110,7 @@ function checkVersion (fn) {
110
110
 
111
111
  const requiredVersion = meta.fastify
112
112
 
113
- const fastifyRc = /-(rc|pre|alpha).+$/.test(this.version)
113
+ const fastifyRc = /-(?:rc|pre|alpha).+$/.test(this.version)
114
114
  if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) {
115
115
  // A Fastify release candidate phase is taking place. In order to reduce
116
116
  // the effort needed to test plugins with the RC, we allow plugins targeting
package/lib/reply.js CHANGED
@@ -32,7 +32,7 @@ const {
32
32
  } = require('./hooks')
33
33
 
34
34
  const internals = require('./handleRequest')[Symbol.for('internals')]
35
- const loggerUtils = require('./logger')
35
+ const loggerUtils = require('./logger-factory')
36
36
  const now = loggerUtils.now
37
37
  const { handleError } = require('./error-handler')
38
38
  const { getSchemaSerializer } = require('./schemas')
@@ -263,8 +263,7 @@ Reply.prototype.header = function (key, value = '') {
263
263
 
264
264
  Reply.prototype.headers = function (headers) {
265
265
  const keys = Object.keys(headers)
266
- /* eslint-disable no-var */
267
- for (var i = 0; i !== keys.length; ++i) {
266
+ for (let i = 0; i !== keys.length; ++i) {
268
267
  const key = keys[i]
269
268
  this.header(key, headers[key])
270
269
  }
@@ -868,9 +867,9 @@ function buildReply (R) {
868
867
  this[kReplyEndTime] = undefined
869
868
  this.log = log
870
869
 
871
- var prop
870
+ let prop
872
871
 
873
- for (var i = 0; i < props.length; i++) {
872
+ for (let i = 0; i < props.length; i++) {
874
873
  prop = props[i]
875
874
  this[prop.key] = prop.value
876
875
  }
package/lib/request.js CHANGED
@@ -73,10 +73,8 @@ function buildRegularRequest (R) {
73
73
  this.log = log
74
74
  this.body = undefined
75
75
 
76
- // eslint-disable-next-line no-var
77
- var prop
78
- // eslint-disable-next-line no-var
79
- for (var i = 0; i < props.length; i++) {
76
+ let prop
77
+ for (let i = 0; i < props.length; i++) {
80
78
  prop = props[i]
81
79
  this[prop.key] = prop.value
82
80
  }
@@ -119,7 +117,11 @@ function buildRequestWithTrustProxy (R, trustProxy) {
119
117
  if (this.ip !== undefined && this.headers['x-forwarded-host']) {
120
118
  return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host'])
121
119
  }
122
- return this.headers.host || this.headers[':authority']
120
+ // the last fallback is used to support the following cases:
121
+ // 1. support http.requireHostHeader === false
122
+ // 2. support HTTP/1.0 without Host Header
123
+ // 3. support headers schema which may remove the Host Header
124
+ return this.headers.host ?? this.headers[':authority'] ?? ''
123
125
  }
124
126
  },
125
127
  protocol: {
@@ -211,23 +213,28 @@ Object.defineProperties(Request.prototype, {
211
213
  },
212
214
  host: {
213
215
  get () {
214
- return this.raw.headers.host || this.raw.headers[':authority']
216
+ // the last fallback is used to support the following cases:
217
+ // 1. support http.requireHostHeader === false
218
+ // 2. support HTTP/1.0 without Host Header
219
+ // 3. support headers schema which may remove the Host Header
220
+ return this.raw.headers.host ?? this.raw.headers[':authority'] ?? ''
215
221
  }
216
222
  },
217
223
  hostname: {
218
224
  get () {
219
- return (this.host).split(':')[0]
225
+ return this.host.split(':', 1)[0]
220
226
  }
221
227
  },
222
228
  port: {
223
229
  get () {
224
230
  // first try taking port from host
225
- const portFromHost = parseInt((this.host).split(':').slice(-1)[0])
231
+ const portFromHost = parseInt(this.host.split(':').slice(-1)[0])
226
232
  if (!isNaN(portFromHost)) {
227
233
  return portFromHost
228
234
  }
229
235
  // now fall back to port from host/:authority header
230
- const portFromHeader = parseInt((this.headers.host || this.headers[':authority']).split(':').slice(-1)[0])
236
+ const host = (this.headers.host ?? this.headers[':authority'] ?? '')
237
+ const portFromHeader = parseInt(host.split(':').slice(-1)[0])
231
238
  if (!isNaN(portFromHeader)) {
232
239
  return portFromHeader
233
240
  }
package/lib/route.js CHANGED
@@ -49,7 +49,7 @@ const {
49
49
  kRouteContext
50
50
  } = require('./symbols.js')
51
51
  const { buildErrorHandler } = require('./error-handler')
52
- const { createChildLogger } = require('./logger')
52
+ const { createChildLogger } = require('./logger-factory.js')
53
53
  const { getGenReqId } = require('./reqIdGenFactory.js')
54
54
 
55
55
  function buildRouting (options) {
@@ -181,45 +181,46 @@ function buildRouting (options) {
181
181
  * @param {{ options: import('../fastify').RouteOptions, isFastify: boolean }}
182
182
  */
183
183
  function route ({ options, isFastify }) {
184
+ throwIfAlreadyStarted('Cannot add route!')
185
+
184
186
  // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
185
187
  const opts = { ...options }
186
188
 
187
- const { exposeHeadRoute } = opts
188
- const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
189
- const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
189
+ const path = opts.url || opts.path || ''
190
190
 
191
- const isGetRoute = opts.method === 'GET' ||
192
- (Array.isArray(opts.method) && opts.method.includes('GET'))
193
- const isHeadRoute = opts.method === 'HEAD' ||
194
- (Array.isArray(opts.method) && opts.method.includes('HEAD'))
191
+ if (!opts.handler) {
192
+ throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path)
193
+ }
195
194
 
196
- // we need to clone a set of initial options for HEAD route
197
- const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
195
+ if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') {
196
+ throw new FST_ERR_ROUTE_HANDLER_NOT_FN(opts.method, path)
197
+ }
198
198
 
199
- throwIfAlreadyStarted('Cannot add route!')
199
+ validateBodyLimitOption(opts.bodyLimit)
200
200
 
201
- const path = opts.url || opts.path || ''
201
+ const shouldExposeHead = opts.exposeHeadRoute ?? globalExposeHeadRoutes
202
+
203
+ let isGetRoute = false
204
+ let isHeadRoute = false
202
205
 
203
206
  if (Array.isArray(opts.method)) {
204
- // eslint-disable-next-line no-var
205
- for (var i = 0; i < opts.method.length; ++i) {
207
+ for (let i = 0; i < opts.method.length; ++i) {
206
208
  opts.method[i] = normalizeAndValidateMethod.call(this, opts.method[i])
207
209
  validateSchemaBodyOption.call(this, opts.method[i], path, opts.schema)
210
+
211
+ isGetRoute = opts.method.includes('GET')
212
+ isHeadRoute = opts.method.includes('HEAD')
208
213
  }
209
214
  } else {
210
215
  opts.method = normalizeAndValidateMethod.call(this, opts.method)
211
216
  validateSchemaBodyOption.call(this, opts.method, path, opts.schema)
212
- }
213
217
 
214
- if (!opts.handler) {
215
- throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path)
218
+ isGetRoute = opts.method === 'GET'
219
+ isHeadRoute = opts.method === 'HEAD'
216
220
  }
217
221
 
218
- if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') {
219
- throw new FST_ERR_ROUTE_HANDLER_NOT_FN(opts.method, path)
220
- }
221
-
222
- validateBodyLimitOption(opts.bodyLimit)
222
+ // we need to clone a set of initial options for HEAD route
223
+ const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
223
224
 
224
225
  const prefix = this[kRoutePrefix]
225
226
 
package/lib/validation.js CHANGED
@@ -7,7 +7,7 @@ const {
7
7
  kSchemaBody: bodySchema,
8
8
  kSchemaResponse: responseSchema
9
9
  } = require('./symbols')
10
- const scChecker = /^[1-5]{1}[0-9]{2}$|^[1-5]xx$|^default$/
10
+ const scChecker = /^[1-5](?:\d{2}|xx)$|^default$/
11
11
 
12
12
  const {
13
13
  FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX
@@ -24,7 +24,7 @@ function compileSchemasForSerialization (context, compile) {
24
24
  .reduce(function (acc, statusCode) {
25
25
  const schema = context.schema.response[statusCode]
26
26
  statusCode = statusCode.toLowerCase()
27
- if (!scChecker.exec(statusCode)) {
27
+ if (!scChecker.test(statusCode)) {
28
28
  throw new FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX()
29
29
  }
30
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "5.0.0",
3
+ "version": "5.2.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -10,24 +10,22 @@
10
10
  "benchmark": "concurrently -k -s first \"node ./examples/benchmark/simple.js\" \"autocannon -c 100 -d 30 -p 10 localhost:3000/\"",
11
11
  "benchmark:parser": "concurrently -k -s first \"node ./examples/benchmark/parser.js\" \"autocannon -c 100 -d 30 -p 10 -i ./examples/benchmark/body.json -H \"content-type:application/jsoff\" -m POST localhost:3000/\"",
12
12
  "build:validation": "node build/build-error-serializer.js && node build/build-validation.js",
13
- "coverage": "npm run unit -- --coverage-report=html",
14
- "coverage:ci": "tap --coverage-report=html --coverage-report=lcov --allow-incomplete-coverage",
15
- "coverage:ci-check-coverage": "tap replay",
13
+ "coverage": "c8 --reporter html borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --lines 100 ",
14
+ "coverage:ci-check-coverage": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --lines 100",
16
15
  "lint": "npm run lint:eslint",
17
16
  "lint:fix": "eslint --fix",
18
17
  "lint:markdown": "markdownlint-cli2",
19
18
  "lint:eslint": "eslint",
20
- "prepublishOnly": "cross-env PREPUBLISH=true tap --allow-incomplete-coverage test/build/**.test.js && npm run test:validator:integrity",
19
+ "prepublishOnly": "cross-env PREPUBLISH=true borp --reporter=./test/test-reporter.mjs && npm run test:validator:integrity",
21
20
  "test": "npm run lint && npm run unit && npm run test:typescript",
22
- "test:ci": "npm run unit -- --coverage-report=lcovonly && npm run test:typescript",
21
+ "test:ci": "npm run unit && npm run test:typescript",
23
22
  "test:report": "npm run lint && npm run unit:report && npm run test:typescript",
24
23
  "test:validator:integrity": "npm run build:validation && git diff --quiet --ignore-all-space --ignore-blank-lines --ignore-cr-at-eol lib/error-serializer.js && git diff --quiet --ignore-all-space --ignore-blank-lines --ignore-cr-at-eol lib/configValidator.js",
25
- "test:typescript": "tsc test/types/import.ts --noEmit && tsd",
24
+ "test:typescript": "tsc test/types/import.ts --target es2022 --moduleResolution node16 --module node16 --noEmit && tsd",
26
25
  "test:watch": "npm run unit -- --watch --coverage-report=none --reporter=terse",
27
- "unit": "tap",
28
- "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml",
29
- "unit:report": "tap --coverage-report=html --coverage-report=cobertura | tee out.tap",
30
- "citgm": "tap --jobs=1 --timeout=120"
26
+ "unit": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage",
27
+ "unit:report": "c8 --reporter html borp --reporter=./test/test-reporter.mjs",
28
+ "citgm": "borp --reporter=./test/test-reporter.mjs --coverage --check-coverage --concurrency=1"
31
29
  },
32
30
  "repository": {
33
31
  "type": "git",
@@ -163,12 +161,13 @@
163
161
  "ajv-i18n": "^4.2.0",
164
162
  "ajv-merge-patch": "^5.0.1",
165
163
  "autocannon": "^7.15.0",
164
+ "borp": "^0.18.0",
166
165
  "branch-comparer": "^1.1.0",
167
166
  "concurrently": "^8.2.2",
168
167
  "cross-env": "^7.0.3",
169
168
  "eslint": "^9.0.0",
170
169
  "fast-json-body": "^1.1.0",
171
- "fastify-plugin": "^4.5.1",
170
+ "fastify-plugin": "^5.0.0",
172
171
  "fluent-json-schema": "^5.0.0",
173
172
  "h2url": "^0.2.0",
174
173
  "http-errors": "^2.0.0",
@@ -179,12 +178,11 @@
179
178
  "neostandard": "^0.11.3",
180
179
  "node-forge": "^1.3.1",
181
180
  "proxyquire": "^2.1.3",
182
- "send": "^0.18.0",
183
181
  "simple-get": "^4.0.1",
184
182
  "split2": "^4.2.0",
185
183
  "tap": "^21.0.0",
186
184
  "tsd": "^0.31.0",
187
- "typescript": "^5.4.5",
185
+ "typescript": "~5.4.5",
188
186
  "undici": "^6.13.0",
189
187
  "vary": "^1.1.2",
190
188
  "yup": "^1.4.0"
@@ -202,7 +200,7 @@
202
200
  "process-warning": "^4.0.0",
203
201
  "proxy-addr": "^2.0.7",
204
202
  "rfdc": "^1.3.1",
205
- "secure-json-parse": "^2.7.0",
203
+ "secure-json-parse": "^3.0.1",
206
204
  "semver": "^7.6.0",
207
205
  "toad-cache": "^3.7.0"
208
206
  },