fastify 4.8.0 → 4.9.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.
@@ -155,6 +155,10 @@ section.
155
155
  - [`@ethicdevs/fastify-git-server`](https://github.com/EthicDevs/fastify-git-server)
156
156
  A plugin to easily create git server and make one/many Git repositories available
157
157
  for clone/fetch/push through the standard `git` (over http) commands.
158
+ - [`@fastify-userland/request-id`](https://github.com/fastify-userland/request-id)
159
+ Fastify Request ID Plugin
160
+ - [`@fastify-userland/typeorm-query-runner`](https://github.com/fastify-userland/typeorm-query-runner)
161
+ Fastify typeorm QueryRunner plugin
158
162
  - [`@gquittet/graceful-server`](https://github.com/gquittet/graceful-server)
159
163
  Tiny (~5k), Fast, KISS, and dependency-free Node.JS library to make your
160
164
  Fastify API graceful.
@@ -235,7 +239,7 @@ section.
235
239
  send HTTP requests via [axios](https://github.com/axios/axios).
236
240
  - [`fastify-babel`](https://github.com/cfware/fastify-babel) Fastify plugin for
237
241
  development servers that require Babel transformations of JavaScript sources.
238
- - [`fastify-bcrypt`](https://github.com/heply/fastify-bcrypt) A Bcrypt hash
242
+ - [`fastify-bcrypt`](https://github.com/beliven-it/fastify-bcrypt) A Bcrypt hash
239
243
  generator & checker.
240
244
  - [`fastify-blipp`](https://github.com/PavelPolyakov/fastify-blipp) Prints your
241
245
  routes to the console, so you definitely know which endpoints are available.
@@ -263,8 +267,8 @@ section.
263
267
  Sequelize ORM.
264
268
  - [`fastify-couchdb`](https://github.com/nigelhanlon/fastify-couchdb) Fastify
265
269
  plugin to add CouchDB support via [nano](https://github.com/apache/nano).
266
- - [`fastify-crud-generator`](https://github.com/heply/fastify-crud-generator) A
267
- plugin to rapidly generate CRUD routes for any entity.
270
+ - [`fastify-crud-generator`](https://github.com/beliven-it/fastify-crud-generator)
271
+ A plugin to rapidly generate CRUD routes for any entity.
268
272
  - [`fastify-custom-healthcheck`](https://github.com/gkampitakis/fastify-custom-healthcheck)
269
273
  Fastify plugin to add health route in your server that asserts custom
270
274
  functions.
@@ -453,7 +457,7 @@ section.
453
457
  Fastify plugin for memoize responses by expressive settings.
454
458
  - [`fastify-piscina`](https://github.com/piscinajs/fastify-piscina) A worker
455
459
  thread pool plugin using [Piscina](https://github.com/piscinajs/piscina).
456
- - [`fastify-polyglot`](https://github.com/heply/fastify-polyglot) A plugin to
460
+ - [`fastify-polyglot`](https://github.com/beliven-it/fastify-polyglot) A plugin to
457
461
  handle i18n using
458
462
  [node-polyglot](https://www.npmjs.com/package/node-polyglot).
459
463
  - [`fastify-postgraphile`](https://github.com/alemagio/fastify-postgraphile)
@@ -547,7 +551,7 @@ section.
547
551
  [Tokenize](https://github.com/Bowser65/Tokenize) plugin for Fastify that
548
552
  removes the pain of managing authentication tokens, with built-in integration
549
553
  for `fastify-auth`.
550
- - [`fastify-totp`](https://github.com/heply/fastify-totp) A plugin to handle
554
+ - [`fastify-totp`](https://github.com/beliven-it/fastify-totp) A plugin to handle
551
555
  TOTP (e.g. for 2FA).
552
556
  - [`fastify-twitch-ebs-tools`](https://github.com/lukemnet/fastify-twitch-ebs-tools)
553
557
  Useful functions for Twitch Extension Backend Services (EBS).
@@ -606,6 +610,8 @@ section.
606
610
  and updated Typeorm plugin for use with Fastify.
607
611
 
608
612
  #### [Community Tools](#community-tools)
613
+ - [`@fastify-userland/workflows`](https://github.com/fastify-userland/workflows)
614
+ Reusable workflows for use in the Fastify plugin
609
615
  - [`fast-maker`](https://github.com/imjuni/fast-maker) route configuration
610
616
  generator by directory structure.
611
617
  - [`simple-tjscli`](https://github.com/imjuni/simple-tjscli) CLI tool to
@@ -20,9 +20,9 @@
20
20
  - [.callNotFound()](#callnotfound)
21
21
  - [.getResponseTime()](#getresponsetime)
22
22
  - [.type(contentType)](#typecontenttype)
23
- - [.getSerializationFunction(schema | httpStatus)](#getserializationfunctionschema--httpstatus)
24
- - [.compileSerializationSchema(schema, httpStatus)](#compileserializationschemaschema-httpstatus)
25
- - [.serializeInput(data, [schema | httpStatus], [httpStatus])](#serializeinputdata-schema--httpstatus-httpstatus)
23
+ - [.getSerializationFunction(schema | httpStatus, [contentType])](#getserializationfunctionschema--httpstatus)
24
+ - [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschemaschema-httpstatus)
25
+ - [.serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])](#serializeinputdata-schema--httpstatus-httpstatus)
26
26
  - [.serializer(func)](#serializerfunc)
27
27
  - [.raw](#raw)
28
28
  - [.sent](#sent)
@@ -63,16 +63,17 @@ object that exposes the following functions and properties:
63
63
  - `.serialize(payload)` - Serializes the specified payload using the default
64
64
  JSON serializer or using the custom serializer (if one is set) and returns the
65
65
  serialized payload.
66
- - `.getSerializationFunction(schema | httpStatus)` - Returns the serialization
66
+ - `.getSerializationFunction(schema | httpStatus, [contentType])` - Returns the serialization
67
67
  function for the specified schema or http status, if any of either are set.
68
- - `.compileSerializationSchema(schema, httpStatus)` - Compiles the specified
69
- schema and returns a serialization function using the default (or customized)
70
- `SerializerCompiler`. The optional `httpStatus` is forwarded to the
71
- `SerializerCompiler` if provided, default to `undefined`.
72
- - `.serializeInput(data, schema, [,httpStatus])` - Serializes the specified data
73
- using the specified schema and returns the serialized payload.
74
- If the optional `httpStatus` is provided, the function will use the serializer
75
- function given for that HTTP Status Code. Default to `undefined`.
68
+ - `.compileSerializationSchema(schema, [httpStatus], [contentType])` - Compiles
69
+ the specified schema and returns a serialization function using the default
70
+ (or customized) `SerializerCompiler`. The optional `httpStatus` is forwarded
71
+ to the `SerializerCompiler` if provided, default to `undefined`.
72
+ - `.serializeInput(data, schema, [,httpStatus], [contentType])` - Serializes
73
+ the specified data using the specified schema and returns the serialized payload.
74
+ If the optional `httpStatus`, and `contentType` are provided, the function
75
+ will use the serializer function given for that specific content type and
76
+ HTTP Status Code. Default to `undefined`.
76
77
  - `.serializer(function)` - Sets a custom serializer for the payload.
77
78
  - `.send(payload)` - Sends the payload to the user, could be a plain text, a
78
79
  buffer, JSON, stream, or an Error object.
@@ -339,12 +340,12 @@ reply.type('text/html')
339
340
  If the `Content-Type` has a JSON subtype, and the charset parameter is not set,
340
341
  `utf-8` will be used as the charset by default.
341
342
 
342
- ### .getSerializationFunction(schema | httpStatus)
343
+ ### .getSerializationFunction(schema | httpStatus, [contentType])
343
344
  <a id="getserializationfunction"></a>
344
345
 
345
346
  By calling this function using a provided `schema` or `httpStatus`,
346
- it will return a `serialzation` function that can be used to
347
- serialize diverse inputs. It returns `undefined` if no
347
+ and the optional `contentType`, it will return a `serialzation` function
348
+ that can be used to serialize diverse inputs. It returns `undefined` if no
348
349
  serialization function was found using either of the provided inputs.
349
350
 
350
351
  This heavily depends of the `schema#responses` attached to the route, or
@@ -367,12 +368,18 @@ serialize({ foo: 'bar' }) // '{"foo":"bar"}'
367
368
  const serialize = reply
368
369
  .getSerializationFunction(200)
369
370
  serialize({ foo: 'bar' }) // '{"foo":"bar"}'
371
+
372
+ // or
373
+
374
+ const serialize = reply
375
+ .getSerializationFunction(200, 'application/json')
376
+ serialize({ foo: 'bar' }) // '{"foo":"bar"}'
370
377
  ```
371
378
 
372
- See [.compileSerializationSchema(schema, [httpStatus])](#compileserializationschema)
379
+ See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
373
380
  for more information on how to compile serialization schemas.
374
381
 
375
- ### .compileSerializationSchema(schema, httpStatus)
382
+ ### .compileSerializationSchema(schema, [httpStatus], [contentType])
376
383
  <a id="compileserializationschema"></a>
377
384
 
378
385
  This function will compile a serialization schema and
@@ -381,9 +388,9 @@ The function returned (a.k.a. _serialization function_) returned is compiled
381
388
  by using the provided `SerializerCompiler`. Also this is cached by using
382
389
  a `WeakMap` for reducing compilation calls.
383
390
 
384
- The optional paramater `httpStatus`, if provided, is forwarded directly
385
- the `SerializerCompiler`, so it can be used to compile the serialization
386
- function if a custom `SerializerCompiler` is used.
391
+ The optional paramaters `httpStatus` and `contentType`, if provided,
392
+ are forwarded directly to the `SerializerCompiler`, so it can be used
393
+ to compile the serialization function if a custom `SerializerCompiler` is used.
387
394
 
388
395
  This heavily depends of the `schema#responses` attached to the route, or
389
396
  the serialization functions compiled by using `compileSerializationSchema`.
@@ -412,6 +419,23 @@ const serialize = reply
412
419
  }
413
420
  }, 200)
414
421
  serialize({ foo: 'bar' }) // '{"foo":"bar"}'
422
+
423
+ // or
424
+
425
+ const serialize = reply
426
+ .compileSerializationSchema({
427
+ '3xx': {
428
+ content: {
429
+ 'application/json': {
430
+ schema: {
431
+ name: { type: 'string' },
432
+ phone: { type: 'number' }
433
+ }
434
+ }
435
+ }
436
+ }
437
+ }, '3xx', 'application/json')
438
+ serialize({ name: 'Jone', phone: 201090909090 }) // '{"name":"Jone", "phone":201090909090}'
415
439
  ```
416
440
 
417
441
  Note that you should be careful when using this function, as it will cache
@@ -461,14 +485,14 @@ const newSerialize = reply.compileSerializationSchema(newSchema)
461
485
  console.log(newSerialize === serialize) // false
462
486
  ```
463
487
 
464
- ### .serializeInput(data, [schema | httpStatus], [httpStatus])
488
+ ### .serializeInput(data, [schema | httpStatus], [httpStatus], [contentType])
465
489
  <a id="serializeinput"></a>
466
490
 
467
491
  This function will serialize the input data based on the provided schema,
468
492
  or http status code. If both provided, the `httpStatus` will take presedence.
469
493
 
470
494
  If there is not a serialization function for a given `schema`, a new serialization
471
- function will be compiled forwarding the `httpStatus` if provided.
495
+ function will be compiled forwarding the `httpStatus`, and the `contentType` if provided.
472
496
 
473
497
  ```js
474
498
  reply
@@ -497,9 +521,14 @@ reply
497
521
 
498
522
  reply
499
523
  .serializeInput({ foo: 'bar'}, 200) // '{"foo":"bar"}'
524
+
525
+ // or
526
+
527
+ reply
528
+ .serializeInput({ name: 'Jone', age: 18 }, '200', 'application/vnd.v1+json') // '{"name": "Jone", "age": 18}'
500
529
  ```
501
530
 
502
- See [.compileSerializationSchema(schema, [httpStatus])](#compileserializationschema)
531
+ See [.compileSerializationSchema(schema, [httpStatus], [contentType])](#compileserializationschema)
503
532
  for more information on how to compile serialization schemas.
504
533
 
505
534
  ### .serializer(func)
@@ -94,8 +94,8 @@ fastify.route(options)
94
94
  schemas for request validations. See the [Validation and
95
95
  Serialization](./Validation-and-Serialization.md#schema-validator)
96
96
  documentation.
97
- * `serializerCompiler({ { schema, method, url, httpStatus } })`: function that
98
- builds schemas for response serialization. See the [Validation and
97
+ * `serializerCompiler({ { schema, method, url, httpStatus, contentType } })`:
98
+ function that builds schemas for response serialization. See the [Validation and
99
99
  Serialization](./Validation-and-Serialization.md#schema-serializer)
100
100
  documentation.
101
101
  * `schemaErrorFormatter(errors, dataVar)`: function that formats the errors from
@@ -1370,7 +1370,7 @@ const fastify = Fastify({
1370
1370
  buildSerializer: function factory (externalSchemas, serializerOptsServerOption) {
1371
1371
  // This factory function must return a schema serializer compiler.
1372
1372
  // See [#schema-serializer](./Validation-and-Serialization.md#schema-serializer) for details.
1373
- return function serializerCompiler ({ schema, method, url, httpStatus }) {
1373
+ return function serializerCompiler ({ schema, method, url, httpStatus, contentType }) {
1374
1374
  return data => JSON.stringify(data)
1375
1375
  }
1376
1376
  }
@@ -611,6 +611,44 @@ const schema = {
611
611
 
612
612
  fastify.post('/the/url', { schema }, handler)
613
613
  ```
614
+ You can even have a specific response schema for different content types.
615
+ For example:
616
+ ```js
617
+ const schema = {
618
+ response: {
619
+ 200: {
620
+ description: 'Response schema that support different content types'
621
+ content: {
622
+ 'application/json': {
623
+ schema: {
624
+ name: { type: 'string' },
625
+ image: { type: 'string' },
626
+ address: { type: 'string' }
627
+ }
628
+ },
629
+ 'application/vnd.v1+json': {
630
+ schema: {
631
+ type: 'array',
632
+ items: { $ref: 'test' }
633
+ }
634
+ }
635
+ }
636
+ },
637
+ '3xx': {
638
+ content: {
639
+ 'application/vnd.v2+json': {
640
+ schema: {
641
+ fullName: { type: 'string' },
642
+ phone: { type: 'string' }
643
+ }
644
+ }
645
+ }
646
+ }
647
+ }
648
+ }
649
+
650
+ fastify.post('/url', { schema }, handler)
651
+ ```
614
652
 
615
653
  #### Serializer Compiler
616
654
  <a id="schema-serializer"></a>
@@ -621,7 +659,7 @@ change the default serialization method by providing a function to serialize
621
659
  every route where you do.
622
660
 
623
661
  ```js
624
- fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => {
662
+ fastify.setSerializerCompiler(({ schema, method, url, httpStatus, contentType }) => {
625
663
  return data => JSON.stringify(data)
626
664
  })
627
665
 
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.8.0'
3
+ const VERSION = '4.9.0'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('http')
@@ -29,9 +29,10 @@ const {
29
29
 
30
30
  function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) {
31
31
  this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)
32
- this.customParsers = {}
33
- this.customParsers['application/json'] = new Parser(true, false, bodyLimit, this[kDefaultJsonParse])
34
- this.customParsers['text/plain'] = new Parser(true, false, bodyLimit, defaultPlainTextParser)
32
+ // using a map instead of a plain object to avoid prototype hijack attacks
33
+ this.customParsers = new Map()
34
+ this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse]))
35
+ this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser))
35
36
  this.parserList = ['application/json', 'text/plain']
36
37
  this.parserRegExpList = []
37
38
  this.cache = lru(100)
@@ -62,35 +63,35 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
62
63
  )
63
64
 
64
65
  if (contentTypeIsString && contentType === '*') {
65
- this.customParsers[''] = parser
66
+ this.customParsers.set('', parser)
66
67
  } else {
67
68
  if (contentTypeIsString) {
68
69
  this.parserList.unshift(contentType)
69
70
  } else {
70
71
  this.parserRegExpList.unshift(contentType)
71
72
  }
72
- this.customParsers[contentType] = parser
73
+ this.customParsers.set(contentType.toString(), parser)
73
74
  }
74
75
  }
75
76
 
76
77
  ContentTypeParser.prototype.hasParser = function (contentType) {
77
- return contentType in this.customParsers
78
+ return this.customParsers.has(typeof contentType === 'string' ? contentType : contentType.toString())
78
79
  }
79
80
 
80
81
  ContentTypeParser.prototype.existingParser = function (contentType) {
81
- if (contentType === 'application/json') {
82
- return this.customParsers['application/json'] && this.customParsers['application/json'].fn !== this[kDefaultJsonParse]
82
+ if (contentType === 'application/json' && this.customParsers.has(contentType)) {
83
+ return this.customParsers.get(contentType).fn !== this[kDefaultJsonParse]
83
84
  }
84
- if (contentType === 'text/plain') {
85
- return this.customParsers['text/plain'] && this.customParsers['text/plain'].fn !== defaultPlainTextParser
85
+ if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
86
+ return this.customParsers.get(contentType).fn !== defaultPlainTextParser
86
87
  }
87
88
 
88
- return contentType in this.customParsers
89
+ return this.hasParser(contentType)
89
90
  }
90
91
 
91
92
  ContentTypeParser.prototype.getParser = function (contentType) {
92
- if (contentType in this.customParsers) {
93
- return this.customParsers[contentType]
93
+ if (this.hasParser(contentType)) {
94
+ return this.customParsers.get(contentType)
94
95
  }
95
96
 
96
97
  if (this.cache.has(contentType)) {
@@ -101,7 +102,7 @@ ContentTypeParser.prototype.getParser = function (contentType) {
101
102
  for (var i = 0; i !== this.parserList.length; ++i) {
102
103
  const parserName = this.parserList[i]
103
104
  if (contentType.indexOf(parserName) !== -1) {
104
- const parser = this.customParsers[parserName]
105
+ const parser = this.customParsers.get(parserName)
105
106
  this.cache.set(contentType, parser)
106
107
  return parser
107
108
  }
@@ -111,17 +112,17 @@ ContentTypeParser.prototype.getParser = function (contentType) {
111
112
  for (var j = 0; j !== this.parserRegExpList.length; ++j) {
112
113
  const parserRegExp = this.parserRegExpList[j]
113
114
  if (parserRegExp.test(contentType)) {
114
- const parser = this.customParsers[parserRegExp]
115
+ const parser = this.customParsers.get(parserRegExp.toString())
115
116
  this.cache.set(contentType, parser)
116
117
  return parser
117
118
  }
118
119
  }
119
120
 
120
- return this.customParsers['']
121
+ return this.customParsers.get('')
121
122
  }
122
123
 
123
124
  ContentTypeParser.prototype.removeAll = function () {
124
- this.customParsers = {}
125
+ this.customParsers = new Map()
125
126
  this.parserRegExpList = []
126
127
  this.parserList = []
127
128
  this.cache = lru(100)
@@ -130,7 +131,7 @@ ContentTypeParser.prototype.removeAll = function () {
130
131
  ContentTypeParser.prototype.remove = function (contentType) {
131
132
  if (!(typeof contentType === 'string' || contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
132
133
 
133
- delete this.customParsers[contentType]
134
+ this.customParsers.delete(contentType.toString())
134
135
 
135
136
  const parsers = typeof contentType === 'string' ? this.parserList : this.parserRegExpList
136
137
 
@@ -289,7 +290,7 @@ function Parser (asString, asBuffer, bodyLimit, fn) {
289
290
  function buildContentTypeParser (c) {
290
291
  const contentTypeParser = new ContentTypeParser()
291
292
  contentTypeParser[kDefaultJsonParse] = c[kDefaultJsonParse]
292
- Object.assign(contentTypeParser.customParsers, c.customParsers)
293
+ contentTypeParser.customParsers = new Map(c.customParsers.entries())
293
294
  contentTypeParser.parserList = c.parserList.slice()
294
295
  return contentTypeParser
295
296
  }
@@ -49,6 +49,8 @@ function handleError (reply, error, cb) {
49
49
  // In case the error handler throws, we set the next errorHandler so we can error again
50
50
  reply[kReplyNextErrorHandler] = Object.getPrototypeOf(errorHandler)
51
51
 
52
+ // we need to remove content-type to allow content-type guessing for serialization
53
+ delete reply[kReplyHeaders]['content-type']
52
54
  delete reply[kReplyHeaders]['content-length']
53
55
 
54
56
  const func = errorHandler.func
@@ -217,7 +217,7 @@ class Serializer {
217
217
  }
218
218
 
219
219
 
220
-
220
+
221
221
 
222
222
  module.exports = main
223
223
 
package/lib/errors.js CHANGED
@@ -97,7 +97,7 @@ const codes = {
97
97
  ),
98
98
  FST_ERR_HOOK_INVALID_HANDLER: createError(
99
99
  'FST_ERR_HOOK_INVALID_HANDLER',
100
- 'The hook callback must be a function',
100
+ '%s hook should be a function, instead got %s',
101
101
  500,
102
102
  TypeError
103
103
  ),
@@ -165,6 +165,10 @@ const codes = {
165
165
  'FST_ERR_MISSING_SERIALIZATION_FN',
166
166
  'Missing serialization function. Key "%s"'
167
167
  ),
168
+ FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN: createError(
169
+ 'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN',
170
+ 'Missing serialization function. Key "%s:%s"'
171
+ ),
168
172
  FST_ERR_REQ_INVALID_VALIDATION_INVOCATION: createError(
169
173
  'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION',
170
174
  'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.'
@@ -181,6 +185,10 @@ const codes = {
181
185
  'FST_ERR_SCH_ALREADY_PRESENT',
182
186
  "Schema with id '%s' already declared!"
183
187
  ),
188
+ FST_ERR_SCH_CONTENT_MISSING_SCHEMA: createError(
189
+ 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA',
190
+ "Schema is missing for the content type '%s'"
191
+ ),
184
192
  FST_ERR_SCH_DUPLICATE: createError(
185
193
  'FST_ERR_SCH_DUPLICATE',
186
194
  "Schema with '%s' already present!"
package/lib/hooks.js CHANGED
@@ -49,10 +49,10 @@ function Hooks () {
49
49
 
50
50
  Hooks.prototype.validate = function (hook, fn) {
51
51
  if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE()
52
- if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER()
53
52
  if (supportedHooks.indexOf(hook) === -1) {
54
53
  throw new Error(`${hook} hook not supported!`)
55
54
  }
55
+ if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof fn)
56
56
  }
57
57
 
58
58
  Hooks.prototype.add = function (hook, fn) {
package/lib/reply.js CHANGED
@@ -44,7 +44,8 @@ const {
44
44
  FST_ERR_BAD_STATUS_CODE,
45
45
  FST_ERR_BAD_TRAILER_NAME,
46
46
  FST_ERR_BAD_TRAILER_VALUE,
47
- FST_ERR_MISSING_SERIALIZATION_FN
47
+ FST_ERR_MISSING_SERIALIZATION_FN,
48
+ FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN
48
49
  } = require('./errors')
49
50
  const warning = require('./warnings')
50
51
 
@@ -312,11 +313,15 @@ Reply.prototype.code = function (code) {
312
313
 
313
314
  Reply.prototype.status = Reply.prototype.code
314
315
 
315
- Reply.prototype.getSerializationFunction = function (schemaOrStatus) {
316
+ Reply.prototype.getSerializationFunction = function (schemaOrStatus, contentType) {
316
317
  let serialize
317
318
 
318
319
  if (typeof schemaOrStatus === 'string' || typeof schemaOrStatus === 'number') {
319
- serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]
320
+ if (typeof contentType === 'string') {
321
+ serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]?.[contentType]
322
+ } else {
323
+ serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]
324
+ }
320
325
  } else if (typeof schemaOrStatus === 'object') {
321
326
  serialize = this[kRouteContext][kReplySerializeWeakMap]?.get(schemaOrStatus)
322
327
  }
@@ -324,7 +329,7 @@ Reply.prototype.getSerializationFunction = function (schemaOrStatus) {
324
329
  return serialize
325
330
  }
326
331
 
327
- Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null) {
332
+ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null, contentType = null) {
328
333
  const { request } = this
329
334
  const { method, url } = request
330
335
 
@@ -346,7 +351,8 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null
346
351
  schema,
347
352
  method,
348
353
  url,
349
- httpStatus
354
+ httpStatus,
355
+ contentType
350
356
  })
351
357
 
352
358
  // We create a WeakMap to compile the schema only once
@@ -363,22 +369,34 @@ Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null
363
369
  return serializeFn
364
370
  }
365
371
 
366
- Reply.prototype.serializeInput = function (input, schema, httpStatus) {
372
+ Reply.prototype.serializeInput = function (input, schema, httpStatus, contentType) {
373
+ const possibleContentType = httpStatus
367
374
  let serialize
368
375
  httpStatus = typeof schema === 'string' || typeof schema === 'number'
369
376
  ? schema
370
377
  : httpStatus
371
378
 
379
+ contentType = httpStatus && possibleContentType !== httpStatus
380
+ ? possibleContentType
381
+ : contentType
382
+
372
383
  if (httpStatus != null) {
373
- serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]
384
+ if (contentType != null) {
385
+ serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]?.[contentType]
386
+ } else {
387
+ serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]
388
+ }
374
389
 
375
- if (serialize == null) throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus)
390
+ if (serialize == null) {
391
+ if (contentType) throw new FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN(httpStatus, contentType)
392
+ throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus)
393
+ }
376
394
  } else {
377
395
  // Check if serialize function already compiled
378
396
  if (this[kRouteContext][kReplySerializeWeakMap]?.has(schema)) {
379
397
  serialize = this[kRouteContext][kReplySerializeWeakMap].get(schema)
380
398
  } else {
381
- serialize = this.compileSerializationSchema(schema, httpStatus)
399
+ serialize = this.compileSerializationSchema(schema, httpStatus, contentType)
382
400
  }
383
401
  }
384
402
 
@@ -483,7 +501,7 @@ function preserializeHookEnd (err, request, reply, payload) {
483
501
  } else if (reply[kRouteContext] && reply[kRouteContext][kReplySerializerDefault]) {
484
502
  payload = reply[kRouteContext][kReplySerializerDefault](payload, reply.raw.statusCode)
485
503
  } else {
486
- payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode)
504
+ payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode, reply[kReplyHeaders]['content-type'])
487
505
  }
488
506
  } catch (e) {
489
507
  wrapSeralizationError(e, reply)
@@ -797,10 +815,11 @@ function notFound (reply) {
797
815
  * @param {object} context the request context
798
816
  * @param {object} data the JSON payload to serialize
799
817
  * @param {number} statusCode the http status code
818
+ * @param {string} contentType the reply content type
800
819
  * @returns {string} the serialized payload
801
820
  */
802
- function serialize (context, data, statusCode) {
803
- const fnSerialize = getSchemaSerializer(context, statusCode)
821
+ function serialize (context, data, statusCode, contentType) {
822
+ const fnSerialize = getSchemaSerializer(context, statusCode, contentType)
804
823
  if (fnSerialize) {
805
824
  return fnSerialize(data)
806
825
  }
package/lib/route.js CHANGED
@@ -20,7 +20,8 @@ const {
20
20
  FST_ERR_DEFAULT_ROUTE_INVALID_TYPE,
21
21
  FST_ERR_DUPLICATED_ROUTE,
22
22
  FST_ERR_INVALID_URL,
23
- FST_ERR_SEND_UNDEFINED_ERR
23
+ FST_ERR_SEND_UNDEFINED_ERR,
24
+ FST_ERR_HOOK_INVALID_HANDLER
24
25
  } = require('./errors')
25
26
 
26
27
  const {
@@ -243,6 +244,20 @@ function buildRouting (options) {
243
244
  }
244
245
  }
245
246
 
247
+ for (const hook of lifecycleHooks) {
248
+ if (opts && hook in opts) {
249
+ if (Array.isArray(opts[hook])) {
250
+ for (const func of opts[hook]) {
251
+ if (typeof func !== 'function') {
252
+ throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof func)
253
+ }
254
+ }
255
+ } else if (typeof opts[hook] !== 'function') {
256
+ throw new FST_ERR_HOOK_INVALID_HANDLER(hook, typeof opts[hook])
257
+ }
258
+ }
259
+ }
260
+
246
261
  const constraints = opts.constraints || {}
247
262
  const config = {
248
263
  ...opts.config,
@@ -15,12 +15,16 @@ function buildSchemaController (parentSchemaCtrl, opts) {
15
15
  return new SchemaController(parentSchemaCtrl, opts)
16
16
  }
17
17
 
18
- let compilersFactory = {
19
- buildValidator: ValidatorSelector(),
20
- buildSerializer: SerializerSelector()
18
+ const compilersFactory = Object.assign({
19
+ buildValidator: null,
20
+ buildSerializer: null
21
+ }, opts?.compilersFactory)
22
+
23
+ if (!compilersFactory.buildValidator) {
24
+ compilersFactory.buildValidator = ValidatorSelector()
21
25
  }
22
- if (opts && opts.compilersFactory) {
23
- compilersFactory = Object.assign(compilersFactory, opts.compilersFactory)
26
+ if (!compilersFactory.buildSerializer) {
27
+ compilersFactory.buildSerializer = SerializerSelector()
24
28
  }
25
29
 
26
30
  const option = {