fastify 3.26.0 → 4.0.0-alpha.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.
Files changed (129) hide show
  1. package/README.md +5 -4
  2. package/build/build-error-serializer.js +27 -0
  3. package/build/build-validation.js +49 -35
  4. package/docs/Guides/Ecosystem.md +2 -1
  5. package/docs/Guides/Prototype-Poisoning.md +3 -3
  6. package/docs/Migration-Guide-V4.md +12 -0
  7. package/docs/Reference/ContentTypeParser.md +8 -1
  8. package/docs/Reference/Errors.md +51 -6
  9. package/docs/Reference/Hooks.md +4 -7
  10. package/docs/Reference/LTS.md +5 -4
  11. package/docs/Reference/Reply.md +23 -22
  12. package/docs/Reference/Request.md +1 -3
  13. package/docs/Reference/Routes.md +17 -10
  14. package/docs/Reference/Server.md +98 -63
  15. package/docs/Reference/TypeScript.md +11 -13
  16. package/docs/Reference/Validation-and-Serialization.md +32 -54
  17. package/docs/Type-Providers.md +257 -0
  18. package/examples/hooks.js +1 -1
  19. package/examples/simple-stream.js +18 -0
  20. package/fastify.d.ts +36 -22
  21. package/fastify.js +72 -53
  22. package/lib/configValidator.js +902 -1023
  23. package/lib/contentTypeParser.js +6 -16
  24. package/lib/context.js +36 -10
  25. package/lib/decorate.js +5 -3
  26. package/lib/error-handler.js +158 -0
  27. package/lib/error-serializer.js +257 -0
  28. package/lib/errors.js +49 -10
  29. package/lib/fourOhFour.js +31 -20
  30. package/lib/handleRequest.js +10 -13
  31. package/lib/hooks.js +14 -9
  32. package/lib/noop-set.js +10 -0
  33. package/lib/pluginOverride.js +0 -3
  34. package/lib/pluginUtils.js +3 -2
  35. package/lib/reply.js +44 -163
  36. package/lib/request.js +13 -10
  37. package/lib/route.js +158 -139
  38. package/lib/schema-controller.js +3 -3
  39. package/lib/schemas.js +27 -1
  40. package/lib/server.js +219 -116
  41. package/lib/symbols.js +6 -4
  42. package/lib/validation.js +2 -1
  43. package/lib/warnings.js +2 -12
  44. package/lib/wrapThenable.js +4 -11
  45. package/package.json +40 -45
  46. package/test/404s.test.js +265 -108
  47. package/test/500s.test.js +2 -2
  48. package/test/async-await.test.js +15 -71
  49. package/test/close.test.js +39 -1
  50. package/test/content-parser.test.js +32 -0
  51. package/test/context-config.test.js +56 -4
  52. package/test/custom-http-server.test.js +14 -7
  53. package/test/custom-parser-async.test.js +0 -65
  54. package/test/custom-parser.test.js +54 -121
  55. package/test/decorator.test.js +1 -3
  56. package/test/delete.test.js +5 -5
  57. package/test/encapsulated-error-handler.test.js +50 -0
  58. package/test/esm/index.test.js +0 -14
  59. package/test/fastify-instance.test.js +4 -4
  60. package/test/fluent-schema.test.js +4 -4
  61. package/test/get.test.js +3 -3
  62. package/test/helper.js +18 -3
  63. package/test/hooks-async.test.js +14 -47
  64. package/test/hooks.on-ready.test.js +9 -4
  65. package/test/hooks.test.js +58 -99
  66. package/test/http2/closing.test.js +5 -11
  67. package/test/http2/unknown-http-method.test.js +3 -9
  68. package/test/https/custom-https-server.test.js +12 -6
  69. package/test/inject.test.js +1 -1
  70. package/test/input-validation.js +2 -2
  71. package/test/internals/all.test.js +2 -2
  72. package/test/internals/contentTypeParser.test.js +4 -4
  73. package/test/internals/handleRequest.test.js +9 -46
  74. package/test/internals/initialConfig.test.js +33 -12
  75. package/test/internals/logger.test.js +1 -1
  76. package/test/internals/reply.test.js +245 -3
  77. package/test/internals/request.test.js +13 -7
  78. package/test/internals/server.test.js +88 -0
  79. package/test/listen.test.js +84 -1
  80. package/test/logger.test.js +98 -58
  81. package/test/maxRequestsPerSocket.test.js +8 -6
  82. package/test/middleware.test.js +2 -25
  83. package/test/noop-set.test.js +19 -0
  84. package/test/nullable-validation.test.js +51 -14
  85. package/test/plugin.test.js +31 -5
  86. package/test/pretty-print.test.js +22 -10
  87. package/test/reply-error.test.js +123 -12
  88. package/test/request-error.test.js +2 -5
  89. package/test/route-hooks.test.js +17 -17
  90. package/test/route-prefix.test.js +2 -1
  91. package/test/route.test.js +216 -20
  92. package/test/router-options.test.js +1 -1
  93. package/test/schema-examples.test.js +11 -5
  94. package/test/schema-feature.test.js +24 -19
  95. package/test/schema-serialization.test.js +50 -9
  96. package/test/schema-special-usage.test.js +14 -81
  97. package/test/schema-validation.test.js +9 -9
  98. package/test/skip-reply-send.test.js +8 -8
  99. package/test/stream.test.js +23 -12
  100. package/test/throw.test.js +8 -5
  101. package/test/trust-proxy.test.js +1 -1
  102. package/test/type-provider.test.js +20 -0
  103. package/test/types/fastify.test-d.ts +12 -18
  104. package/test/types/hooks.test-d.ts +7 -3
  105. package/test/types/import.js +2 -0
  106. package/test/types/import.ts +1 -0
  107. package/test/types/instance.test-d.ts +61 -15
  108. package/test/types/logger.test-d.ts +44 -15
  109. package/test/types/route.test-d.ts +8 -2
  110. package/test/types/schema.test-d.ts +2 -39
  111. package/test/types/type-provider.test-d.ts +417 -0
  112. package/test/validation-error-handling.test.js +9 -9
  113. package/test/versioned-routes.test.js +29 -17
  114. package/test/wrapThenable.test.js +7 -6
  115. package/types/.eslintrc.json +1 -1
  116. package/types/content-type-parser.d.ts +17 -8
  117. package/types/hooks.d.ts +107 -60
  118. package/types/instance.d.ts +137 -105
  119. package/types/logger.d.ts +18 -104
  120. package/types/plugin.d.ts +10 -4
  121. package/types/register.d.ts +1 -1
  122. package/types/reply.d.ts +16 -11
  123. package/types/request.d.ts +10 -5
  124. package/types/route.d.ts +42 -31
  125. package/types/schema.d.ts +15 -1
  126. package/types/type-provider.d.ts +99 -0
  127. package/types/utils.d.ts +1 -1
  128. package/lib/schema-compilers.js +0 -12
  129. package/test/emit-warning.test.js +0 -166
package/lib/route.js CHANGED
@@ -18,7 +18,9 @@ const {
18
18
  const {
19
19
  FST_ERR_SCH_VALIDATION_BUILD,
20
20
  FST_ERR_SCH_SERIALIZATION_BUILD,
21
- FST_ERR_DEFAULT_ROUTE_INVALID_TYPE
21
+ FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
22
+ FST_ERR_DUPLICATED_ROUTE,
23
+ FST_ERR_INVALID_URL
22
24
  } = require('./errors')
23
25
 
24
26
  const {
@@ -26,21 +28,20 @@ const {
26
28
  kLogLevel,
27
29
  kLogSerializers,
28
30
  kHooks,
29
- kHooksDeprecatedPreParsing,
30
31
  kSchemaController,
31
32
  kOptions,
32
- kContentTypeParser,
33
- kReply,
34
33
  kReplySerializerDefault,
35
34
  kReplyIsError,
36
- kRequest,
37
35
  kRequestPayloadStream,
38
36
  kDisableRequestLogging,
39
37
  kSchemaErrorFormatter,
40
- kErrorHandler
38
+ kErrorHandler,
39
+ kHasBeenDecorated
41
40
  } = require('./symbols.js')
41
+ const { buildErrorHandler } = require('./error-handler')
42
42
 
43
43
  function buildRouting (options) {
44
+ const { keepAliveConnections } = options
44
45
  const router = FindMyWay(options.config)
45
46
 
46
47
  let avvio
@@ -98,6 +99,10 @@ function buildRouting (options) {
98
99
 
99
100
  // Convert shorthand to extended route declaration
100
101
  function prepareRoute (method, url, options, handler) {
102
+ if (typeof url !== 'string') {
103
+ throw new FST_ERR_INVALID_URL(typeof url)
104
+ }
105
+
101
106
  if (!handler && typeof options === 'function') {
102
107
  handler = options // for support over direct function calls such as fastify.get() options are reused as the handler
103
108
  options = {}
@@ -130,22 +135,17 @@ function buildRouting (options) {
130
135
 
131
136
  throwIfAlreadyStarted('Cannot add route when fastify instance is already started!')
132
137
 
138
+ const path = opts.url || opts.path || ''
139
+
133
140
  if (Array.isArray(opts.method)) {
134
141
  // eslint-disable-next-line no-var
135
142
  for (var i = 0; i < opts.method.length; ++i) {
136
- const method = opts.method[i]
137
- if (supportedMethods.indexOf(method) === -1) {
138
- throw new Error(`${method} method is not supported!`)
139
- }
143
+ validateMethodAndSchemaBodyOption(opts.method[i], path, opts.schema)
140
144
  }
141
145
  } else {
142
- if (supportedMethods.indexOf(opts.method) === -1) {
143
- throw new Error(`${opts.method} method is not supported!`)
144
- }
146
+ validateMethodAndSchemaBodyOption(opts.method, path, opts.schema)
145
147
  }
146
148
 
147
- const path = opts.url || opts.path
148
-
149
149
  if (!opts.handler) {
150
150
  throw new Error(`Missing handler function for ${opts.method}:${path} route.`)
151
151
  }
@@ -158,42 +158,33 @@ function buildRouting (options) {
158
158
 
159
159
  const prefix = this[kRoutePrefix]
160
160
 
161
- this.after((notHandledErr, done) => {
162
- if (path === '/' && prefix.length && opts.method !== 'HEAD') {
163
- switch (opts.prefixTrailingSlash) {
164
- case 'slash':
165
- afterRouteAdded.call(this, { path }, notHandledErr, done)
166
- break
167
- case 'no-slash':
168
- afterRouteAdded.call(this, { path: '' }, notHandledErr, done)
169
- break
170
- case 'both':
171
- default:
172
- afterRouteAdded.call(this, { path: '' }, notHandledErr, done)
173
- // If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
174
- if (ignoreTrailingSlash !== true) {
175
- afterRouteAdded.call(this, { path, prefixing: true }, notHandledErr, done)
176
- }
177
- }
178
- } else if (path && path[0] === '/' && prefix.endsWith('/')) {
179
- // Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
180
- afterRouteAdded.call(this, { path: path.slice(1) }, notHandledErr, done)
181
- } else {
182
- afterRouteAdded.call(this, { path }, notHandledErr, done)
161
+ if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
162
+ switch (opts.prefixTrailingSlash) {
163
+ case 'slash':
164
+ addNewRoute.call(this, path)
165
+ break
166
+ case 'no-slash':
167
+ addNewRoute.call(this, '')
168
+ break
169
+ case 'both':
170
+ default:
171
+ addNewRoute.call(this, '')
172
+ // If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
173
+ if (ignoreTrailingSlash !== true) {
174
+ addNewRoute.call(this, path, true)
175
+ }
183
176
  }
184
- })
177
+ } else if (path[0] === '/' && prefix.endsWith('/')) {
178
+ // Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
179
+ addNewRoute.call(this, path.slice(1))
180
+ } else {
181
+ addNewRoute.call(this, path)
182
+ }
185
183
 
186
184
  // chainable api
187
185
  return this
188
186
 
189
- /**
190
- * This function sets up a new route, its log serializers, and triggers route hooks.
191
- *
192
- * @param {object} opts contains route `path` and `prefixing` flag which indicates if this is an auto-prefixed route, e.g. `fastify.register(routes, { prefix: '/foo' })`
193
- * @param {*} notHandledErr error object to be passed back to the original invoker
194
- * @param {*} done callback
195
- */
196
- function afterRouteAdded ({ path, prefixing = false }, notHandledErr, done) {
187
+ function addNewRoute (path, prefixing = false) {
197
188
  const url = prefix + path
198
189
 
199
190
  opts.url = url
@@ -211,117 +202,122 @@ function buildRouting (options) {
211
202
  }
212
203
 
213
204
  if (prefixing === false) {
214
- // run 'onRoute' hooks
205
+ // run 'onRoute' hooks
215
206
  for (const hook of this[kHooks].onRoute) {
216
- try {
217
- hook.call(this, opts)
218
- } catch (error) {
219
- done(error)
220
- return
221
- }
207
+ hook.call(this, opts)
222
208
  }
223
209
  }
224
210
 
211
+ const constraints = opts.constraints || {}
225
212
  const config = {
226
213
  ...opts.config,
227
214
  url,
228
215
  method: opts.method
229
216
  }
230
- const constraints = opts.constraints || {}
217
+
218
+ const context = new Context({
219
+ schema: opts.schema,
220
+ handler: opts.handler.bind(this),
221
+ config,
222
+ errorHandler: opts.errorHandler,
223
+ bodyLimit: opts.bodyLimit,
224
+ logLevel: opts.logLevel,
225
+ logSerializers: opts.logSerializers,
226
+ attachValidation: opts.attachValidation,
227
+ schemaErrorFormatter: opts.schemaErrorFormatter,
228
+ replySerializer: this[kReplySerializerDefault],
229
+ server: this
230
+ })
231
+
231
232
  if (opts.version) {
232
233
  warning.emit('FSTDEP008')
233
234
  constraints.version = opts.version
234
235
  }
235
236
 
236
- const context = new Context(
237
- opts.schema,
238
- opts.handler.bind(this),
239
- this[kReply],
240
- this[kRequest],
241
- this[kContentTypeParser],
242
- config,
243
- opts.errorHandler || this[kErrorHandler],
244
- opts.bodyLimit,
245
- opts.logLevel,
246
- opts.logSerializers,
247
- opts.attachValidation,
248
- this[kReplySerializerDefault],
249
- opts.schemaErrorFormatter || this[kSchemaErrorFormatter]
250
- )
237
+ const headRouteExists = opts.method === 'HEAD' && router.find(opts.method, opts.url, constraints) != null
251
238
 
252
- const headRouteExists = router.find('HEAD', url, constraints) != null
239
+ // Check if the current route is not for a sibling HEAD one
240
+ if (!headRouteExists) {
241
+ try {
242
+ router.on(opts.method, opts.url, { constraints }, routeHandler, context)
243
+ } catch (error) {
244
+ const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route '${opts.url}'`)
245
+ if (isDuplicatedRoute) {
246
+ throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
247
+ }
253
248
 
254
- try {
255
- router.on(opts.method, opts.url, { constraints }, routeHandler, context)
256
- } catch (err) {
257
- done(err)
258
- return
249
+ throw error
250
+ }
259
251
  }
260
252
 
261
- const { exposeHeadRoute } = opts
262
- const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
263
- const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
253
+ this.after((notHandledErr, done) => {
254
+ // Send context async
255
+ context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
256
+ context._parserOptions.limit = opts.bodyLimit || null
257
+ context.logLevel = opts.logLevel
258
+ context.logSerializers = opts.logSerializers
259
+ context.attachValidation = opts.attachValidation
260
+ context[kReplySerializerDefault] = this[kReplySerializerDefault]
261
+ context.schemaErrorFormatter = opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter
262
+
263
+ // Run hooks and more
264
+ avvio.once('preReady', () => {
265
+ for (const hook of lifecycleHooks) {
266
+ const toSet = this[kHooks][hook]
267
+ .concat(opts[hook] || [])
268
+ .map(h => h.bind(this))
269
+ context[hook] = toSet.length ? toSet : null
270
+ }
264
271
 
265
- if (shouldExposeHead && options.method === 'GET' && !headRouteExists) {
266
- const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
267
- prepareRoute.call(this, 'HEAD', path, { ...opts, onSend: onSendHandlers })
268
- } else if (headRouteExists && exposeHeadRoute) {
269
- warning.emit('FSTDEP007')
270
- }
272
+ // Optimization: avoid encapsulation if no decoration has been done.
273
+ while (!context.Request[kHasBeenDecorated] && context.Request.parent) {
274
+ context.Request = context.Request.parent
275
+ }
276
+ while (!context.Reply[kHasBeenDecorated] && context.Reply.parent) {
277
+ context.Reply = context.Reply.parent
278
+ }
271
279
 
272
- // It can happen that a user registers a plugin with some hooks *after*
273
- // the route registration. To be sure to also load those hooks,
274
- // we must listen for the avvio's preReady event, and update the context object accordingly.
275
- avvio.once('preReady', () => {
276
- for (const hook of lifecycleHooks) {
277
- const toSet = this[kHooks][hook]
278
- .concat(opts[hook] || [])
279
- .map(h => {
280
- const bound = h.bind(this)
281
-
282
- // Track hooks deprecation markers
283
- if (hook === 'preParsing') {
284
- // Check for deprecation syntax
285
- if (h.length === (h.constructor.name === 'AsyncFunction' ? 2 : 3)) {
286
- warning.emit('FSTDEP004')
287
- bound[kHooksDeprecatedPreParsing] = true
288
- }
289
- }
290
-
291
- return bound
292
- })
293
- context[hook] = toSet.length ? toSet : null
294
- }
280
+ // Must store the 404 Context in 'preReady' because it is only guaranteed to
281
+ // be available after all of the plugins and routes have been loaded.
282
+ fourOhFour.setContext(this, context)
295
283
 
296
- // Must store the 404 Context in 'preReady' because it is only guaranteed to
297
- // be available after all of the plugins and routes have been loaded.
298
- fourOhFour.setContext(this, context)
284
+ if (opts.schema) {
285
+ context.schema = normalizeSchema(context.schema, this.initialConfig)
299
286
 
300
- if (opts.schema) {
301
- context.schema = normalizeSchema(context.schema, this.initialConfig)
287
+ const schemaController = this[kSchemaController]
288
+ if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) {
289
+ schemaController.setupValidator(this[kOptions])
290
+ }
291
+ try {
292
+ compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler)
293
+ } catch (error) {
294
+ throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
295
+ }
302
296
 
303
- const schemaController = this[kSchemaController]
304
- if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) {
305
- schemaController.setupValidator(this[kOptions])
306
- }
307
- try {
308
- compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler)
309
- } catch (error) {
310
- throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
297
+ if (opts.schema.response && !opts.serializerCompiler) {
298
+ schemaController.setupSerializer(this[kOptions])
299
+ }
300
+ try {
301
+ compileSchemasForSerialization(context, opts.serializerCompiler || schemaController.serializerCompiler)
302
+ } catch (error) {
303
+ throw new FST_ERR_SCH_SERIALIZATION_BUILD(opts.method, url, error.message)
304
+ }
311
305
  }
306
+ })
312
307
 
313
- if (opts.schema.response && !opts.serializerCompiler) {
314
- schemaController.setupSerializer(this[kOptions])
315
- }
316
- try {
317
- compileSchemasForSerialization(context, opts.serializerCompiler || schemaController.serializerCompiler)
318
- } catch (error) {
319
- throw new FST_ERR_SCH_SERIALIZATION_BUILD(opts.method, url, error.message)
320
- }
308
+ const { exposeHeadRoute } = opts
309
+ const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
310
+ const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
311
+
312
+ if (shouldExposeHead && options.method === 'GET' && !headRouteExists) {
313
+ const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
314
+ prepareRoute.call(this, 'HEAD', path, { ...opts, onSend: onSendHandlers })
315
+ } else if (headRouteExists && exposeHeadRoute) {
316
+ warning.emit('FSTDEP007')
321
317
  }
322
- })
323
318
 
324
- done(notHandledErr)
319
+ done(notHandledErr)
320
+ })
325
321
  }
326
322
  }
327
323
 
@@ -345,6 +341,17 @@ function buildRouting (options) {
345
341
  }
346
342
  }
347
343
 
344
+ // When server.forceCloseConnections is true, we will collect any requests
345
+ // that have indicated they want persistence so that they can be reaped
346
+ // on server close. Otherwise, the container is a noop container.
347
+ const connHeader = String.prototype.toLowerCase.call(req.headers.connection || '')
348
+ if (connHeader === 'keep-alive') {
349
+ if (keepAliveConnections.has(req.socket) === false) {
350
+ keepAliveConnections.add(req.socket)
351
+ req.socket.on('close', removeTrackedSocket.bind({ keepAliveConnections, socket: req.socket }))
352
+ }
353
+ }
354
+
348
355
  // we revert the changes in defaultRoute
349
356
  if (req.headers[kRequestAcceptVersion] !== undefined) {
350
357
  req.headers['accept-version'] = req.headers[kRequestAcceptVersion]
@@ -412,6 +419,16 @@ function handleTimeout () {
412
419
  )
413
420
  }
414
421
 
422
+ function validateMethodAndSchemaBodyOption (method, path, schema) {
423
+ if (supportedMethods.indexOf(method) === -1) {
424
+ throw new Error(`${method} method is not supported!`)
425
+ }
426
+
427
+ if ((method === 'GET' || method === 'HEAD') && schema && schema.body) {
428
+ throw new Error(`Body validation schema for ${method}:${path} route is not supported!`)
429
+ }
430
+ }
431
+
415
432
  function validateBodyLimitOption (bodyLimit) {
416
433
  if (bodyLimit === undefined) return
417
434
  if (!Number.isInteger(bodyLimit) || bodyLimit <= 0) {
@@ -422,6 +439,7 @@ function validateBodyLimitOption (bodyLimit) {
422
439
  function runPreParsing (err, request, reply) {
423
440
  if (reply.sent === true) return
424
441
  if (err != null) {
442
+ reply[kReplyIsError] = true
425
443
  reply.send(err)
426
444
  return
427
445
  }
@@ -448,10 +466,6 @@ function preParsingHookRunner (functions, request, reply, cb) {
448
466
  }
449
467
 
450
468
  if (err || i === functions.length) {
451
- if (err && !(err instanceof Error)) {
452
- reply[kReplyIsError] = true
453
- }
454
-
455
469
  cb(err, request, reply)
456
470
  return
457
471
  }
@@ -459,11 +473,7 @@ function preParsingHookRunner (functions, request, reply, cb) {
459
473
  const fn = functions[i++]
460
474
  let result
461
475
  try {
462
- if (fn[kHooksDeprecatedPreParsing]) {
463
- result = fn(request, reply, next)
464
- } else {
465
- result = fn(request, reply, request[kRequestPayloadStream], next)
466
- }
476
+ result = fn(request, reply, request[kRequestPayloadStream], next)
467
477
  } catch (error) {
468
478
  next(error)
469
479
  return
@@ -485,6 +495,15 @@ function preParsingHookRunner (functions, request, reply, cb) {
485
495
  next(null, request[kRequestPayloadStream])
486
496
  }
487
497
 
498
+ /**
499
+ * Used within the route handler as a `net.Socket.close` event handler.
500
+ * The purpose is to remove a socket from the tracked sockets collection when
501
+ * the socket has naturally timed out.
502
+ */
503
+ function removeTrackedSocket () {
504
+ this.keepAliveConnections.delete(this.socket)
505
+ }
506
+
488
507
  function noop () { }
489
508
 
490
509
  module.exports = { buildRouting, validateBodyLimitOption }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { buildSchemas } = require('./schemas')
4
- const { serializerCompiler } = require('./schema-compilers')
4
+ const SerializerSelector = require('@fastify/fast-json-stringify-compiler')
5
5
  const ValidatorSelector = require('@fastify/ajv-compiler')
6
6
 
7
7
  /**
@@ -17,7 +17,7 @@ function buildSchemaController (parentSchemaCtrl, opts) {
17
17
 
18
18
  let compilersFactory = {
19
19
  buildValidator: ValidatorSelector(),
20
- buildSerializer: serializerCompiler
20
+ buildSerializer: SerializerSelector()
21
21
  }
22
22
  if (opts && opts.compilersFactory) {
23
23
  compilersFactory = Object.assign(compilersFactory, opts.compilersFactory)
@@ -25,7 +25,7 @@ function buildSchemaController (parentSchemaCtrl, opts) {
25
25
 
26
26
  const option = {
27
27
  bucket: (opts && opts.bucket) || buildSchemas,
28
- compilersFactory: compilersFactory
28
+ compilersFactory
29
29
  }
30
30
 
31
31
  return new SchemaController(undefined, option)
package/lib/schemas.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const fastClone = require('rfdc')({ circles: false, proto: true })
4
- const { kSchemaVisited } = require('./symbols')
4
+ const { kSchemaVisited, kSchemaResponse } = require('./symbols')
5
5
  const kFluentSchema = Symbol.for('fluent-schema-object')
6
6
 
7
7
  const {
@@ -121,7 +121,33 @@ function getSchemaAnyway (schema, jsonShorthand) {
121
121
  return schema
122
122
  }
123
123
 
124
+ /**
125
+ * Search for the right JSON schema compiled function in the request context
126
+ * setup by the route configuration `schema.response`.
127
+ * It will look for the exact match (eg 200) or generic (eg 2xx)
128
+ *
129
+ * @param {object} context the request context
130
+ * @param {number} statusCode the http status code
131
+ * @returns {function|boolean} the right JSON Schema function to serialize
132
+ * the reply or false if it is not set
133
+ */
134
+ function getSchemaSerializer (context, statusCode) {
135
+ const responseSchemaDef = context[kSchemaResponse]
136
+ if (!responseSchemaDef) {
137
+ return false
138
+ }
139
+ if (responseSchemaDef[statusCode]) {
140
+ return responseSchemaDef[statusCode]
141
+ }
142
+ const fallbackStatusCode = (statusCode + '')[0] + 'xx'
143
+ if (responseSchemaDef[fallbackStatusCode]) {
144
+ return responseSchemaDef[fallbackStatusCode]
145
+ }
146
+ return false
147
+ }
148
+
124
149
  module.exports = {
125
150
  buildSchemas (initStore) { return new Schemas(initStore) },
151
+ getSchemaSerializer,
126
152
  normalizeSchema
127
153
  }