fastify 4.6.0 → 4.7.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 (39) hide show
  1. package/docs/Guides/Ecosystem.md +19 -8
  2. package/docs/Guides/Plugins-Guide.md +44 -0
  3. package/docs/Reference/Reply.md +5 -5
  4. package/docs/Reference/Request.md +19 -6
  5. package/docs/Reference/Routes.md +2 -2
  6. package/docs/Reference/Type-Providers.md +3 -3
  7. package/fastify.js +1 -1
  8. package/lib/contentTypeParser.js +10 -9
  9. package/lib/context.js +27 -3
  10. package/lib/error-handler.js +7 -3
  11. package/lib/handleRequest.js +15 -13
  12. package/lib/reply.js +42 -34
  13. package/lib/request.js +36 -18
  14. package/lib/route.js +16 -10
  15. package/lib/schema-controller.js +7 -1
  16. package/lib/server.js +2 -2
  17. package/lib/symbols.js +2 -0
  18. package/lib/validation.js +4 -3
  19. package/lib/warnings.js +2 -0
  20. package/package.json +26 -26
  21. package/test/404s.test.js +19 -1
  22. package/test/context-config.test.js +2 -1
  23. package/test/handler-context.test.js +12 -30
  24. package/test/internals/contentTypeParser.test.js +3 -3
  25. package/test/internals/handleRequest.test.js +4 -3
  26. package/test/internals/reply-serialize.test.js +9 -9
  27. package/test/internals/reply.test.js +10 -9
  28. package/test/internals/request-validate.test.js +97 -9
  29. package/test/internals/request.test.js +57 -3
  30. package/test/internals/validation.test.js +15 -0
  31. package/test/plugin.test.js +1 -1
  32. package/test/reply-code.test.js +59 -0
  33. package/test/route.test.js +29 -0
  34. package/test/router-options.test.js +33 -66
  35. package/test/schema-feature.test.js +25 -0
  36. package/test/schema-validation.test.js +19 -0
  37. package/test/search.test.js +169 -30
  38. package/test/server.test.js +54 -0
  39. package/types/request.d.ts +9 -3
package/lib/reply.js CHANGED
@@ -20,7 +20,8 @@ const {
20
20
  kSchemaResponse,
21
21
  kReplySerializeWeakMap,
22
22
  kSchemaController,
23
- kOptions
23
+ kOptions,
24
+ kRouteContext
24
25
  } = require('./symbols.js')
25
26
  const { hookRunner, hookIterator, onSendHookRunner } = require('./hooks')
26
27
 
@@ -63,14 +64,21 @@ function Reply (res, request, log) {
63
64
  Reply.props = []
64
65
 
65
66
  Object.defineProperties(Reply.prototype, {
67
+ [kRouteContext]: {
68
+ get () {
69
+ return this.request[kRouteContext]
70
+ }
71
+ },
72
+ // TODO: remove once v5 is done
73
+ // Is temporary to avoid constant conflicts between `next` and `main`
66
74
  context: {
67
75
  get () {
68
- return this.request.context
76
+ return this.request[kRouteContext]
69
77
  }
70
78
  },
71
79
  server: {
72
80
  get () {
73
- return this.request.context.server
81
+ return this.request[kRouteContext].server
74
82
  }
75
83
  },
76
84
  sent: {
@@ -292,7 +300,7 @@ Reply.prototype.removeTrailer = function (key) {
292
300
  }
293
301
 
294
302
  Reply.prototype.code = function (code) {
295
- const intValue = parseInt(code)
303
+ const intValue = Number(code)
296
304
  if (isNaN(intValue) || intValue < 100 || intValue > 599) {
297
305
  throw new FST_ERR_BAD_STATUS_CODE(code || String(code))
298
306
  }
@@ -308,9 +316,9 @@ Reply.prototype.getSerializationFunction = function (schemaOrStatus) {
308
316
  let serialize
309
317
 
310
318
  if (typeof schemaOrStatus === 'string' || typeof schemaOrStatus === 'number') {
311
- serialize = this.context[kSchemaResponse]?.[schemaOrStatus]
319
+ serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]
312
320
  } else if (typeof schemaOrStatus === 'object') {
313
- serialize = this.context[kReplySerializeWeakMap]?.get(schemaOrStatus)
321
+ serialize = this[kRouteContext][kReplySerializeWeakMap]?.get(schemaOrStatus)
314
322
  }
315
323
 
316
324
  return serialize
@@ -321,11 +329,11 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null
321
329
  const { method, url } = request
322
330
 
323
331
  // Check if serialize function already compiled
324
- if (this.context[kReplySerializeWeakMap]?.has(schema)) {
325
- return this.context[kReplySerializeWeakMap].get(schema)
332
+ if (this[kRouteContext][kReplySerializeWeakMap]?.has(schema)) {
333
+ return this[kRouteContext][kReplySerializeWeakMap].get(schema)
326
334
  }
327
335
 
328
- const serializerCompiler = this.context.serializerCompiler ||
336
+ const serializerCompiler = this[kRouteContext].serializerCompiler ||
329
337
  this.server[kSchemaController].serializerCompiler ||
330
338
  (
331
339
  // We compile the schemas if no custom serializerCompiler is provided
@@ -346,11 +354,11 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null
346
354
  // if it is not used
347
355
  // TODO: Explore a central cache for all the schemas shared across
348
356
  // encapsulated contexts
349
- if (this.context[kReplySerializeWeakMap] == null) {
350
- this.context[kReplySerializeWeakMap] = new WeakMap()
357
+ if (this[kRouteContext][kReplySerializeWeakMap] == null) {
358
+ this[kRouteContext][kReplySerializeWeakMap] = new WeakMap()
351
359
  }
352
360
 
353
- this.context[kReplySerializeWeakMap].set(schema, serializeFn)
361
+ this[kRouteContext][kReplySerializeWeakMap].set(schema, serializeFn)
354
362
 
355
363
  return serializeFn
356
364
  }
@@ -362,13 +370,13 @@ Reply.prototype.serializeInput = function (input, schema, httpStatus) {
362
370
  : httpStatus
363
371
 
364
372
  if (httpStatus != null) {
365
- serialize = this.context[kSchemaResponse]?.[httpStatus]
373
+ serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]
366
374
 
367
375
  if (serialize == null) throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus)
368
376
  } else {
369
377
  // Check if serialize function already compiled
370
- if (this.context[kReplySerializeWeakMap]?.has(schema)) {
371
- serialize = this.context[kReplySerializeWeakMap].get(schema)
378
+ if (this[kRouteContext][kReplySerializeWeakMap]?.has(schema)) {
379
+ serialize = this[kRouteContext][kReplySerializeWeakMap].get(schema)
372
380
  } else {
373
381
  serialize = this.compileSerializationSchema(schema, httpStatus)
374
382
  }
@@ -381,10 +389,10 @@ Reply.prototype.serialize = function (payload) {
381
389
  if (this[kReplySerializer] !== null) {
382
390
  return this[kReplySerializer](payload)
383
391
  } else {
384
- if (this.context && this.context[kReplySerializerDefault]) {
385
- return this.context[kReplySerializerDefault](payload, this.raw.statusCode)
392
+ if (this[kRouteContext] && this[kRouteContext][kReplySerializerDefault]) {
393
+ return this[kRouteContext][kReplySerializerDefault](payload, this.raw.statusCode)
386
394
  } else {
387
- return serialize(this.context, payload, this.raw.statusCode)
395
+ return serialize(this[kRouteContext], payload, this.raw.statusCode)
388
396
  }
389
397
  }
390
398
  }
@@ -450,9 +458,9 @@ Reply.prototype.then = function (fulfilled, rejected) {
450
458
  }
451
459
 
452
460
  function preserializeHook (reply, payload) {
453
- if (reply.context.preSerialization !== null) {
461
+ if (reply[kRouteContext].preSerialization !== null) {
454
462
  onSendHookRunner(
455
- reply.context.preSerialization,
463
+ reply[kRouteContext].preSerialization,
456
464
  reply.request,
457
465
  reply,
458
466
  payload,
@@ -472,10 +480,10 @@ function preserializeHookEnd (err, request, reply, payload) {
472
480
  try {
473
481
  if (reply[kReplySerializer] !== null) {
474
482
  payload = reply[kReplySerializer](payload)
475
- } else if (reply.context && reply.context[kReplySerializerDefault]) {
476
- payload = reply.context[kReplySerializerDefault](payload, reply.raw.statusCode)
483
+ } else if (reply[kRouteContext] && reply[kRouteContext][kReplySerializerDefault]) {
484
+ payload = reply[kRouteContext][kReplySerializerDefault](payload, reply.raw.statusCode)
477
485
  } else {
478
- payload = serialize(reply.context, payload, reply.raw.statusCode)
486
+ payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode)
479
487
  }
480
488
  } catch (e) {
481
489
  wrapSeralizationError(e, reply)
@@ -487,13 +495,13 @@ function preserializeHookEnd (err, request, reply, payload) {
487
495
  }
488
496
 
489
497
  function wrapSeralizationError (error, reply) {
490
- error.serialization = reply.context.config
498
+ error.serialization = reply[kRouteContext].config
491
499
  }
492
500
 
493
501
  function onSendHook (reply, payload) {
494
- if (reply.context.onSend !== null) {
502
+ if (reply[kRouteContext].onSend !== null) {
495
503
  onSendHookRunner(
496
- reply.context.onSend,
504
+ reply[kRouteContext].onSend,
497
505
  reply.request,
498
506
  reply,
499
507
  payload,
@@ -562,7 +570,7 @@ function onSendEnd (reply, payload) {
562
570
  const contentLength = reply[kReplyHeaders]['content-length']
563
571
  if (!contentLength ||
564
572
  (req.raw.method !== 'HEAD' &&
565
- parseInt(contentLength, 10) !== Buffer.byteLength(payload)
573
+ Number(contentLength) !== Buffer.byteLength(payload)
566
574
  )
567
575
  ) {
568
576
  reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
@@ -660,10 +668,10 @@ function sendStreamTrailer (payload, res, reply) {
660
668
  }
661
669
 
662
670
  function onErrorHook (reply, error, cb) {
663
- if (reply.context.onError !== null && !reply[kReplyNextErrorHandler]) {
671
+ if (reply[kRouteContext].onError !== null && !reply[kReplyNextErrorHandler]) {
664
672
  reply[kReplyIsRunningOnErrorHook] = true
665
673
  onSendHookRunner(
666
- reply.context.onError,
674
+ reply[kRouteContext].onError,
667
675
  reply.request,
668
676
  reply,
669
677
  error,
@@ -682,7 +690,7 @@ function setupResponseListeners (reply) {
682
690
  reply.raw.removeListener('finish', onResFinished)
683
691
  reply.raw.removeListener('error', onResFinished)
684
692
 
685
- const ctx = reply.context
693
+ const ctx = reply[kRouteContext]
686
694
 
687
695
  if (ctx && ctx.onResponse !== null) {
688
696
  hookRunner(
@@ -759,18 +767,18 @@ function buildReply (R) {
759
767
  }
760
768
 
761
769
  function notFound (reply) {
762
- if (reply.context[kFourOhFourContext] === null) {
770
+ if (reply[kRouteContext][kFourOhFourContext] === null) {
763
771
  reply.log.warn('Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.')
764
772
  reply.code(404).send('404 Not Found')
765
773
  return
766
774
  }
767
775
 
768
- reply.request.context = reply.context[kFourOhFourContext]
776
+ reply.request[kRouteContext] = reply[kRouteContext][kFourOhFourContext]
769
777
 
770
778
  // preHandler hook
771
- if (reply.context.preHandler !== null) {
779
+ if (reply[kRouteContext].preHandler !== null) {
772
780
  hookRunner(
773
- reply.context.preHandler,
781
+ reply[kRouteContext].preHandler,
774
782
  hookIterator,
775
783
  reply.request,
776
784
  reply,
package/lib/request.js CHANGED
@@ -11,7 +11,9 @@ const {
11
11
  kSchemaQuerystring,
12
12
  kSchemaController,
13
13
  kOptions,
14
- kRequestValidateWeakMap
14
+ kRequestValidateWeakMap,
15
+ kRouteContext,
16
+ kPublicRouteContext
15
17
  } = require('./symbols')
16
18
  const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors')
17
19
 
@@ -25,7 +27,7 @@ const HTTP_PART_SYMBOL_MAP = {
25
27
 
26
28
  function Request (id, params, req, query, log, context) {
27
29
  this.id = id
28
- this.context = context
30
+ this[kRouteContext] = context
29
31
  this.params = params
30
32
  this.raw = req
31
33
  this.query = query
@@ -66,7 +68,7 @@ function buildRegularRequest (R) {
66
68
  const props = [...R.props]
67
69
  function _Request (id, params, req, query, log, context) {
68
70
  this.id = id
69
- this.context = context
71
+ this[kRouteContext] = context
70
72
  this.params = params
71
73
  this.raw = req
72
74
  this.query = query
@@ -139,7 +141,7 @@ function buildRequestWithTrustProxy (R, trustProxy) {
139
141
  Object.defineProperties(Request.prototype, {
140
142
  server: {
141
143
  get () {
142
- return this.context.server
144
+ return this[kRouteContext].server
143
145
  }
144
146
  },
145
147
  url: {
@@ -152,19 +154,35 @@ Object.defineProperties(Request.prototype, {
152
154
  return this.raw.method
153
155
  }
154
156
  },
157
+ context: {
158
+ get () {
159
+ warning.emit('FSTDEP012')
160
+ return this[kRouteContext]
161
+ }
162
+ },
155
163
  routerPath: {
156
164
  get () {
157
- return this.context.config.url
165
+ return this[kRouteContext].config.url
158
166
  }
159
167
  },
160
168
  routerMethod: {
161
169
  get () {
162
- return this.context.config.method
170
+ return this[kRouteContext].config.method
171
+ }
172
+ },
173
+ routeConfig: {
174
+ get () {
175
+ return this[kRouteContext][kPublicRouteContext].config
176
+ }
177
+ },
178
+ routeSchema: {
179
+ get () {
180
+ return this[kRouteContext][kPublicRouteContext].schema
163
181
  }
164
182
  },
165
183
  is404: {
166
184
  get () {
167
- return this.context.config.url === undefined
185
+ return this[kRouteContext].config.url === undefined
168
186
  }
169
187
  },
170
188
  connection: {
@@ -215,9 +233,9 @@ Object.defineProperties(Request.prototype, {
215
233
  value: function (httpPartOrSchema) {
216
234
  if (typeof httpPartOrSchema === 'string') {
217
235
  const symbol = HTTP_PART_SYMBOL_MAP[httpPartOrSchema]
218
- return this.context[symbol]
236
+ return this[kRouteContext][symbol]
219
237
  } else if (typeof httpPartOrSchema === 'object') {
220
- return this.context[kRequestValidateWeakMap]?.get(httpPartOrSchema)
238
+ return this[kRouteContext][kRequestValidateWeakMap]?.get(httpPartOrSchema)
221
239
  }
222
240
  }
223
241
  },
@@ -225,11 +243,11 @@ Object.defineProperties(Request.prototype, {
225
243
  value: function (schema, httpPart = null) {
226
244
  const { method, url } = this
227
245
 
228
- if (this.context[kRequestValidateWeakMap]?.has(schema)) {
229
- return this.context[kRequestValidateWeakMap].get(schema)
246
+ if (this[kRouteContext][kRequestValidateWeakMap]?.has(schema)) {
247
+ return this[kRouteContext][kRequestValidateWeakMap].get(schema)
230
248
  }
231
249
 
232
- const validatorCompiler = this.context.validatorCompiler ||
250
+ const validatorCompiler = this[kRouteContext].validatorCompiler ||
233
251
  this.server[kSchemaController].validatorCompiler ||
234
252
  (
235
253
  // We compile the schemas if no custom validatorCompiler is provided
@@ -250,11 +268,11 @@ Object.defineProperties(Request.prototype, {
250
268
  // if it is not used
251
269
  // TODO: Explore a central cache for all the schemas shared across
252
270
  // encapsulated contexts
253
- if (this.context[kRequestValidateWeakMap] == null) {
254
- this.context[kRequestValidateWeakMap] = new WeakMap()
271
+ if (this[kRouteContext][kRequestValidateWeakMap] == null) {
272
+ this[kRouteContext][kRequestValidateWeakMap] = new WeakMap()
255
273
  }
256
274
 
257
- this.context[kRequestValidateWeakMap].set(schema, validateFn)
275
+ this[kRouteContext][kRequestValidateWeakMap].set(schema, validateFn)
258
276
 
259
277
  return validateFn
260
278
  }
@@ -268,7 +286,7 @@ Object.defineProperties(Request.prototype, {
268
286
 
269
287
  if (symbol) {
270
288
  // Validate using the HTTP Request Part schema
271
- validate = this.context[symbol]
289
+ validate = this[kRouteContext][symbol]
272
290
  }
273
291
 
274
292
  // We cannot compile if the schema is missed
@@ -280,8 +298,8 @@ Object.defineProperties(Request.prototype, {
280
298
  }
281
299
 
282
300
  if (validate == null) {
283
- if (this.context[kRequestValidateWeakMap]?.has(schema)) {
284
- validate = this.context[kRequestValidateWeakMap].get(schema)
301
+ if (this[kRouteContext][kRequestValidateWeakMap]?.has(schema)) {
302
+ validate = this[kRouteContext][kRequestValidateWeakMap].get(schema)
285
303
  } else {
286
304
  // We proceed to compile if there's no validate function yet
287
305
  validate = this.compileValidationSchema(schema, httpPart)
package/lib/route.js CHANGED
@@ -8,7 +8,6 @@ const { supportedMethods } = require('./httpMethods')
8
8
  const { normalizeSchema } = require('./schemas')
9
9
  const { parseHeadOnSendHandlers } = require('./headRoute')
10
10
  const warning = require('./warnings')
11
- const { kRequestAcceptVersion, kRouteByFastify } = require('./symbols')
12
11
 
13
12
  const {
14
13
  compileSchemasForValidation,
@@ -37,7 +36,10 @@ const {
37
36
  kDisableRequestLogging,
38
37
  kSchemaErrorFormatter,
39
38
  kErrorHandler,
40
- kHasBeenDecorated
39
+ kHasBeenDecorated,
40
+ kRequestAcceptVersion,
41
+ kRouteByFastify,
42
+ kRouteContext
41
43
  } = require('./symbols.js')
42
44
  const { buildErrorHandler } = require('./error-handler')
43
45
 
@@ -155,6 +157,12 @@ function buildRouting (options) {
155
157
  // Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
156
158
  const opts = { ...options }
157
159
 
160
+ const { exposeHeadRoute } = opts
161
+ const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
162
+ const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
163
+ // we need to clone a set of initial options for HEAD route
164
+ const headOpts = shouldExposeHead && options.method === 'GET' ? { ...options } : null
165
+
158
166
  throwIfAlreadyStarted('Cannot add route when fastify instance is already started!')
159
167
 
160
168
  const path = opts.url || opts.path || ''
@@ -321,7 +329,8 @@ function buildRouting (options) {
321
329
  schemaController.setupValidator(this[kOptions])
322
330
  }
323
331
  try {
324
- compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler)
332
+ const isCustom = typeof opts?.validatorCompiler === 'function' || schemaController.isCustomValidatorCompiler
333
+ compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler, isCustom)
325
334
  } catch (error) {
326
335
  throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
327
336
  }
@@ -342,13 +351,10 @@ function buildRouting (options) {
342
351
 
343
352
  // register head route in sync
344
353
  // we must place it after the `this.after`
345
- const { exposeHeadRoute } = opts
346
- const hasRouteExposeHeadRouteFlag = exposeHeadRoute != null
347
- const shouldExposeHead = hasRouteExposeHeadRouteFlag ? exposeHeadRoute : globalExposeHeadRoutes
348
354
 
349
355
  if (shouldExposeHead && options.method === 'GET' && !hasHEADHandler) {
350
- const onSendHandlers = parseHeadOnSendHandlers(opts.onSend)
351
- prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...opts, onSend: onSendHandlers }, isFastify: true })
356
+ const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend)
357
+ prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true })
352
358
  } else if (hasHEADHandler && exposeHeadRoute) {
353
359
  warning.emit('FSTDEP007')
354
360
  }
@@ -490,8 +496,8 @@ function runPreParsing (err, request, reply) {
490
496
 
491
497
  request[kRequestPayloadStream] = request.raw
492
498
 
493
- if (reply.context.preParsing !== null) {
494
- preParsingHookRunner(reply.context.preParsing, request, reply, handleRequest)
499
+ if (request[kRouteContext].preParsing !== null) {
500
+ preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest)
495
501
  } else {
496
502
  handleRequest(null, request, reply)
497
503
  }
@@ -25,7 +25,9 @@ function buildSchemaController (parentSchemaCtrl, opts) {
25
25
 
26
26
  const option = {
27
27
  bucket: (opts && opts.bucket) || buildSchemas,
28
- compilersFactory
28
+ compilersFactory,
29
+ isCustomValidatorCompiler: typeof opts?.compilersFactory?.buildValidator === 'function',
30
+ isCustomSerializerCompiler: typeof opts?.compilersFactory?.buildValidator === 'function'
29
31
  }
30
32
 
31
33
  return new SchemaController(undefined, option)
@@ -37,6 +39,8 @@ class SchemaController {
37
39
  this.addedSchemas = false
38
40
 
39
41
  this.compilersFactory = this.opts.compilersFactory
42
+ this.isCustomValidatorCompiler = this.opts.isCustomValidatorCompiler || false
43
+ this.isCustomSerializerCompiler = this.opts.isCustomSerializerCompiler || false
40
44
 
41
45
  if (parent) {
42
46
  this.schemaBucket = this.opts.bucket(parent.getSchemas())
@@ -65,10 +69,12 @@ class SchemaController {
65
69
  // Schema Controller compilers holder
66
70
  setValidatorCompiler (validatorCompiler) {
67
71
  this.validatorCompiler = validatorCompiler
72
+ this.isCustomValidatorCompiler = true
68
73
  }
69
74
 
70
75
  setSerializerCompiler (serializerCompiler) {
71
76
  this.serializerCompiler = serializerCompiler
77
+ this.isCustomSerializerCompiler = true
72
78
  }
73
79
 
74
80
  getValidatorCompiler () {
package/lib/server.js CHANGED
@@ -331,8 +331,8 @@ function normalizeListenArgs (args) {
331
331
  }
332
332
 
333
333
  function normalizePort (firstArg) {
334
- const port = parseInt(firstArg, 10)
335
- return port >= 0 && !Number.isNaN(port) ? port : 0
334
+ const port = Number(firstArg)
335
+ return port >= 0 && !Number.isNaN(port) && Number.isInteger(port) ? port : 0
336
336
  }
337
337
 
338
338
  function logServerAddress (server) {
package/lib/symbols.js CHANGED
@@ -14,6 +14,8 @@ const keys = {
14
14
  kOptions: Symbol('fastify.options'),
15
15
  kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
16
16
  kPluginNameChain: Symbol('fastify.pluginNameChain'),
17
+ kRouteContext: Symbol('fastify.context'),
18
+ kPublicRouteContext: Symbol('fastify.routeOptions'),
17
19
  // Schema
18
20
  kSchemaController: Symbol('fastify.schemaController'),
19
21
  kSchemaHeaders: Symbol('headers-schema'),
package/lib/validation.js CHANGED
@@ -32,7 +32,7 @@ function compileSchemasForSerialization (context, compile) {
32
32
  }, {})
33
33
  }
34
34
 
35
- function compileSchemasForValidation (context, compile) {
35
+ function compileSchemasForValidation (context, compile, isCustom) {
36
36
  const { schema } = context
37
37
  if (!schema) {
38
38
  return
@@ -41,8 +41,9 @@ function compileSchemasForValidation (context, compile) {
41
41
  const { method, url } = context.config || {}
42
42
 
43
43
  const headers = schema.headers
44
- if (headers && Object.getPrototypeOf(headers) !== Object.prototype) {
45
- // do not mess with non-literals, e.g. Joi schemas
44
+ // the or part is used for backward compatibility
45
+ if (headers && (isCustom || Object.getPrototypeOf(headers) !== Object.prototype)) {
46
+ // do not mess with schema when custom validator applied, e.g. Joi, Typebox
46
47
  context[headersSchema] = compile({ schema: headers, method, url, httpPart: 'headers' })
47
48
  } else if (headers) {
48
49
  // The header keys are case insensitive
package/lib/warnings.js CHANGED
@@ -21,4 +21,6 @@ warning.create('FastifyDeprecation', 'FSTDEP010', 'Modifying the "reply.sent" pr
21
21
 
22
22
  warning.create('FastifyDeprecation', 'FSTDEP011', 'Variadic listen method is deprecated. Please use ".listen(optionsObject)" instead. The variadic signature will be removed in `fastify@5`.')
23
23
 
24
+ warning.create('FastifyDeprecation', 'FSTDEP012', 'Request#context property access is deprecated. Please use "Request#routeConfig" or "Request#routeSchema" instead for accessing Route settings. The "Request#context" will be removed in `fastify@5`.')
25
+
24
26
  module.exports = warning
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -126,35 +126,35 @@
126
126
  "homepage": "https://www.fastify.io/",
127
127
  "devDependencies": {
128
128
  "@fastify/pre-commit": "^2.0.2",
129
- "@sinclair/typebox": "^0.24.9",
129
+ "@sinclair/typebox": "^0.24.41",
130
130
  "@sinonjs/fake-timers": "^9.1.2",
131
- "@types/node": "^18.0.0",
132
- "@typescript-eslint/eslint-plugin": "^5.27.0",
133
- "@typescript-eslint/parser": "^5.27.0",
131
+ "@types/node": "^18.7.18",
132
+ "@typescript-eslint/eslint-plugin": "^5.37.0",
133
+ "@typescript-eslint/parser": "^5.37.0",
134
134
  "ajv": "^8.11.0",
135
135
  "ajv-errors": "^3.0.0",
136
136
  "ajv-formats": "^2.1.1",
137
137
  "ajv-i18n": "^4.2.0",
138
138
  "ajv-merge-patch": "^5.0.1",
139
139
  "branch-comparer": "^1.1.0",
140
- "eslint": "^8.16.0",
141
- "eslint-config-standard": "^17.0.0-1",
140
+ "eslint": "^8.23.1",
141
+ "eslint-config-standard": "^17.0.0",
142
142
  "eslint-import-resolver-node": "^0.3.6",
143
143
  "eslint-plugin-import": "^2.26.0",
144
- "eslint-plugin-n": "^15.2.0",
145
- "eslint-plugin-promise": "^6.0.0",
144
+ "eslint-plugin-n": "^15.2.5",
145
+ "eslint-plugin-promise": "^6.0.1",
146
146
  "fast-json-body": "^1.1.0",
147
- "fast-json-stringify": "^5.0.0",
148
- "fastify-plugin": "^4.0.0",
147
+ "fast-json-stringify": "^5.3.0",
148
+ "fastify-plugin": "^4.2.1",
149
149
  "fluent-json-schema": "^3.1.0",
150
150
  "form-data": "^4.0.0",
151
151
  "h2url": "^0.2.0",
152
152
  "http-errors": "^2.0.0",
153
153
  "joi": "^17.6.0",
154
- "json-schema-to-ts": "^2.5.3",
154
+ "json-schema-to-ts": "^2.5.5",
155
155
  "JSONStream": "^1.3.5",
156
156
  "license-checker": "^25.0.1",
157
- "markdownlint-cli2": "^0.5.0",
157
+ "markdownlint-cli2": "^0.5.1",
158
158
  "proxyquire": "^2.1.3",
159
159
  "pump": "^3.0.0",
160
160
  "self-cert": "^2.0.0",
@@ -162,29 +162,29 @@
162
162
  "simple-get": "^4.0.1",
163
163
  "snazzy": "^9.0.0",
164
164
  "split2": "^4.1.0",
165
- "standard": "^17.0.0-2",
166
- "tap": "^16.2.0",
167
- "tsd": "^0.23.0",
168
- "typescript": "^4.7.2",
169
- "undici": "^5.4.0",
165
+ "standard": "^17.0.0",
166
+ "tap": "^16.3.0",
167
+ "tsd": "^0.24.1",
168
+ "typescript": "^4.8.3",
169
+ "undici": "^5.10.0",
170
170
  "vary": "^1.1.2",
171
171
  "yup": "^0.32.11"
172
172
  },
173
173
  "dependencies": {
174
- "@fastify/ajv-compiler": "^3.1.1",
174
+ "@fastify/ajv-compiler": "^3.3.1",
175
175
  "@fastify/error": "^3.0.0",
176
- "@fastify/fast-json-stringify-compiler": "^4.0.0",
176
+ "@fastify/fast-json-stringify-compiler": "^4.1.0",
177
177
  "abstract-logging": "^2.0.1",
178
- "avvio": "^8.1.3",
179
- "find-my-way": "^7.0.0",
180
- "light-my-request": "^5.5.1",
181
- "pino": "^8.0.0",
178
+ "avvio": "^8.2.0",
179
+ "find-my-way": "^7.2.0",
180
+ "light-my-request": "^5.6.1",
181
+ "pino": "^8.5.0",
182
182
  "process-warning": "^2.0.0",
183
183
  "proxy-addr": "^2.0.7",
184
184
  "rfdc": "^1.3.0",
185
- "secure-json-parse": "^2.4.0",
185
+ "secure-json-parse": "^2.5.0",
186
186
  "semver": "^7.3.7",
187
- "tiny-lru": "^8.0.2"
187
+ "tiny-lru": "^9.0.2"
188
188
  },
189
189
  "standard": {
190
190
  "ignore": [
package/test/404s.test.js CHANGED
@@ -6,6 +6,7 @@ const fp = require('fastify-plugin')
6
6
  const sget = require('simple-get').concat
7
7
  const errors = require('http-errors')
8
8
  const split = require('split2')
9
+ const FormData = require('form-data')
9
10
  const Fastify = require('..')
10
11
 
11
12
  function getUrl (app) {
@@ -18,7 +19,7 @@ function getUrl (app) {
18
19
  }
19
20
 
20
21
  test('default 404', t => {
21
- t.plan(4)
22
+ t.plan(5)
22
23
 
23
24
  const test = t.test
24
25
  const fastify = Fastify()
@@ -74,6 +75,23 @@ test('default 404', t => {
74
75
  t.equal(response.headers['content-type'], 'application/json; charset=utf-8')
75
76
  })
76
77
  })
78
+
79
+ test('using post method and multipart/formdata', t => {
80
+ t.plan(3)
81
+ const form = FormData()
82
+ form.append('test-field', 'just some field')
83
+
84
+ sget({
85
+ method: 'POST',
86
+ url: getUrl(fastify) + '/notSupported',
87
+ body: form,
88
+ json: false
89
+ }, (err, response, body) => {
90
+ t.error(err)
91
+ t.equal(response.statusCode, 404)
92
+ t.equal(response.headers['content-type'], 'application/json; charset=utf-8')
93
+ })
94
+ })
77
95
  })
78
96
  })
79
97
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  const t = require('tap')
4
4
  const test = t.test
5
+ const { kRouteContext } = require('../lib/symbols')
5
6
  const Fastify = require('..')
6
7
 
7
8
  const schema = {
@@ -13,7 +14,7 @@ const schema = {
13
14
  }
14
15
 
15
16
  function handler (req, reply) {
16
- reply.send(reply.context.config)
17
+ reply.send(reply[kRouteContext].config)
17
18
  }
18
19
 
19
20
  test('config', t => {