fastify 4.28.1 → 4.29.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.
package/SPONSORS.md CHANGED
@@ -14,6 +14,8 @@ _Be the first!_
14
14
  ## Tier 3
15
15
 
16
16
  - [Mercedes-Benz Group](https://github.com/mercedes-benz)
17
+ - [Val Town, Inc.](https://opencollective.com/valtown)
18
+ - [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024)
17
19
 
18
20
  ## Tier 2
19
21
 
@@ -6,6 +6,34 @@ Before migrating to v4, please ensure that you have fixed all deprecation
6
6
  warnings from v3. All v3 deprecations have been removed and they will no longer
7
7
  work after upgrading.
8
8
 
9
+ ## Codemods
10
+ ### Fastify v4 Codemods
11
+
12
+ To help with the upgrade, we’ve worked with the team at
13
+ [Codemod](https://github.com/codemod-com/codemod) to
14
+ publish codemods that will automatically update your code to many of
15
+ the new APIs and patterns in Fastify v4.
16
+
17
+ Run the following
18
+ [migration recipe](https://go.codemod.com/fastify-4-migration-recipe) to
19
+ automatically update your code to Fastify v4:
20
+
21
+ ```
22
+ npx codemod@latest fastify/4/migration-recipe
23
+ ```
24
+
25
+ This will run the following codemods:
26
+
27
+ - [`fastify/4/remove-app-use`](https://go.codemod.com/fastify-4-remove-app-use)
28
+ - [`fastify/4/reply-raw-access`](https://go.codemod.com/fastify-4-reply-raw-access)
29
+ - [`fastify/4/wrap-routes-plugin`](https://go.codemod.com/fastify-4-wrap-routes-plugin)
30
+ - [`fastify/4/await-register-calls`](https://go.codemod.com/fastify-4-await-register-calls)
31
+
32
+ Each of these codemods automates the changes listed in the v4 migration guide.
33
+ For a complete list of available Fastify codemods and further details,
34
+ see [Codemod Registry](https://go.codemod.com/fastify).
35
+
36
+
9
37
  ## Breaking Changes
10
38
 
11
39
  ### Error handling composition ([#3261](https://github.com/fastify/fastify/pull/3261))
@@ -55,11 +83,23 @@ If you need to use middleware, use
55
83
  continue to be maintained.
56
84
  However, it is strongly recommended that you migrate to Fastify's [hooks](../Reference/Hooks.md).
57
85
 
86
+ > **Note**: Codemod remove `app.use()` with:
87
+ >
88
+ > ```bash
89
+ > npx codemod@latest fastify/4/remove-app-use
90
+ > ```
91
+
58
92
  ### `reply.res` moved to `reply.raw`
59
93
 
60
94
  If you previously used the `reply.res` attribute to access the underlying Request
61
95
  object you will now need to use `reply.raw`.
62
96
 
97
+ > **Note**: Codemod `reply.res` to `reply.raw` with:
98
+ >
99
+ > ```bash
100
+ > npx codemod@latest fastify/4/reply-raw-access
101
+ > ```
102
+
63
103
  ### Need to `return reply` to signal a "fork" of the promise chain
64
104
 
65
105
  In some situations, like when a response is sent asynchronously or when you are
@@ -105,6 +145,11 @@ As a result, if you specify an `onRoute` hook in a plugin you should now either:
105
145
  done();
106
146
  });
107
147
  ```
148
+ > **Note**: Codemod synchronous route definitions with:
149
+ >
150
+ > ```bash
151
+ > npx codemod@latest fastify/4/wrap-routes-plugin
152
+ > ```
108
153
 
109
154
  * use `await register(...)`
110
155
 
@@ -130,6 +175,13 @@ As a result, if you specify an `onRoute` hook in a plugin you should now either:
130
175
  });
131
176
  ```
132
177
 
178
+ > **Note**: Codemod 'await register(...)' with:
179
+ >
180
+ > ```bash
181
+ > npx codemod@latest fastify/4/await-register-calls
182
+ > ```
183
+
184
+
133
185
  ### Optional URL parameters
134
186
 
135
187
  If you've already used any implicitly optional parameters, you'll get a 404
@@ -43,7 +43,8 @@ fastify.route(options)
43
43
  [here](./Validation-and-Serialization.md) for more info.
44
44
 
45
45
  * `body`: validates the body of the request if it is a POST, PUT, PATCH,
46
- TRACE, SEARCH, PROPFIND, PROPPATCH or LOCK method.
46
+ TRACE, SEARCH, PROPFIND, PROPPATCH, COPY, MOVE, MKCOL, REPORT, MKCALENDAR
47
+ or LOCK method.
47
48
  * `querystring` or `query`: validates the querystring. This can be a complete
48
49
  JSON Schema object, with the property `type` of `object` and `properties`
49
50
  object of parameters, or simply the values of what would be contained in the
@@ -272,6 +272,8 @@ fastify.get('///foo//bar//', function (req, reply) {
272
272
  ### `maxParamLength`
273
273
  <a id="factory-max-param-length"></a>
274
274
 
275
+ + Default: `100`
276
+
275
277
  You can set a custom length for parameters in parametric (standard, regex, and
276
278
  multi) routes by using `maxParamLength` option; the default value is 100
277
279
  characters. If the maximum length limit is reached, the not found route will
@@ -235,6 +235,28 @@ const schema = {
235
235
  fastify.post('/the/url', { schema }, handler)
236
236
  ```
237
237
 
238
+ For `body` schema, it is further possible to differentiate the schema per content
239
+ type by nesting the schemas inside `content` property. The schema validation
240
+ will be applied based on the `Content-Type` header in the request.
241
+
242
+ ```js
243
+ fastify.post('/the/url', {
244
+ schema: {
245
+ body: {
246
+ content: {
247
+ 'application/json': {
248
+ schema: { type: 'object' }
249
+ },
250
+ 'text/plain': {
251
+ schema: { type: 'string' }
252
+ }
253
+ // Other content types will not be validated
254
+ }
255
+ }
256
+ }
257
+ }, handler)
258
+ ```
259
+
238
260
  *Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) the values
239
261
  to the types specified in your schema `type` keywords, both to pass the
240
262
  validation and to use the correctly typed data afterwards.*
@@ -25,6 +25,7 @@
25
25
  - [FSTDEP019](#FSTDEP019)
26
26
  - [FSTDEP020](#FSTDEP020)
27
27
  - [FSTDEP021](#FSTDEP021)
28
+ - [FSTDEP022](#FSTDEP022)
28
29
 
29
30
 
30
31
  ## Warnings
@@ -92,3 +93,4 @@ Deprecation codes are further supported by the Node.js CLI options:
92
93
  | <a id="FSTDEP019">FSTDEP019</a> | You are accessing the deprecated `reply.context` property. | Use `reply.routeOptions.config` or `reply.routeOptions.schema`. | [#5032](https://github.com/fastify/fastify/pull/5032) [#5084](https://github.com/fastify/fastify/pull/5084) |
93
94
  | <a id="FSTDEP020">FSTDEP020</a> | You are using the deprecated `reply.getReponseTime()` method. | Use the `reply.elapsedTime` property instead. | [#5263](https://github.com/fastify/fastify/pull/5263) |
94
95
  | <a id="FSTDEP021">FSTDEP021</a> | The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. | [#5483](https://github.com/fastify/fastify/pull/5483) |
96
+ | <a id="FSTDEP022">FSTDEP022</a> | You are using the deprecated json shorthand schema on route %s. Specify full object schema instead. It will be removed in `fastify@v5` | [#5483](https://github.com/fastify/fastify/pull/0000) |
package/fastify.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- const VERSION = '4.28.1'
3
+ const VERSION = '4.29.1'
4
4
 
5
5
  const Avvio = require('avvio')
6
6
  const http = require('node:http')
@@ -671,7 +671,7 @@ function fastify (options) {
671
671
  }
672
672
 
673
673
  if (name === 'onClose') {
674
- this.onClose(fn)
674
+ this.onClose(fn.bind(this))
675
675
  } else if (name === 'onReady' || name === 'onListen' || name === 'onRoute') {
676
676
  this[kHooks].add(name, fn)
677
677
  } else {
@@ -28,7 +28,8 @@ function handleRequest (err, request, reply) {
28
28
  const contentType = headers['content-type']
29
29
 
30
30
  if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH' ||
31
- method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK' || method === 'REPORT' || method === 'MKCALENDAR') {
31
+ method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK' || method === 'COPY' || method === 'MOVE' ||
32
+ method === 'MKCOL' || method === 'REPORT' || method === 'MKCALENDAR') {
32
33
  if (contentType === undefined) {
33
34
  if (
34
35
  headers['transfer-encoding'] === undefined &&
package/lib/reply.js CHANGED
@@ -602,14 +602,30 @@ function onSendEnd (reply, payload) {
602
602
  reply.header('Trailer', header.trim())
603
603
  }
604
604
 
605
- // since Response contain status code, we need to update before
606
- // any action that used statusCode
607
- const isResponse = toString.call(payload) === '[object Response]'
608
- if (isResponse) {
605
+ // since Response contain status code, headers and body,
606
+ // we need to update the status, add the headers and use it's body as payload
607
+ // before continuing
608
+ if (toString.call(payload) === '[object Response]') {
609
609
  // https://developer.mozilla.org/en-US/docs/Web/API/Response/status
610
610
  if (typeof payload.status === 'number') {
611
611
  reply.code(payload.status)
612
612
  }
613
+
614
+ // https://developer.mozilla.org/en-US/docs/Web/API/Response/headers
615
+ if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') {
616
+ for (const [headerName, headerValue] of payload.headers) {
617
+ reply.header(headerName, headerValue)
618
+ }
619
+ }
620
+
621
+ // https://developer.mozilla.org/en-US/docs/Web/API/Response/body
622
+ if (payload.body !== null) {
623
+ if (payload.bodyUsed) {
624
+ throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED()
625
+ }
626
+ }
627
+ // Keep going, body is either null or ReadableStream
628
+ payload = payload.body
613
629
  }
614
630
  const statusCode = res.statusCode
615
631
 
@@ -656,26 +672,6 @@ function onSendEnd (reply, payload) {
656
672
  return
657
673
  }
658
674
 
659
- // Response
660
- if (isResponse) {
661
- // https://developer.mozilla.org/en-US/docs/Web/API/Response/headers
662
- if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') {
663
- for (const [headerName, headerValue] of payload.headers) {
664
- reply.header(headerName, headerValue)
665
- }
666
- }
667
-
668
- // https://developer.mozilla.org/en-US/docs/Web/API/Response/body
669
- if (payload.body != null) {
670
- if (payload.bodyUsed) {
671
- throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED()
672
- }
673
- // Response.body always a ReadableStream
674
- sendWebStream(payload.body, res, reply)
675
- }
676
- return
677
- }
678
-
679
675
  if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) {
680
676
  throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
681
677
  }
@@ -726,7 +722,7 @@ function sendStream (payload, res, reply) {
726
722
  if (res.headersSent || reply.request.raw.aborted === true) {
727
723
  if (!errorLogged) {
728
724
  errorLogged = true
729
- logStreamError(reply.log, err, res)
725
+ logStreamError(reply.log, err, reply)
730
726
  }
731
727
  res.destroy()
732
728
  } else {
package/lib/route.js CHANGED
@@ -366,7 +366,7 @@ function buildRouting (options) {
366
366
  // any route insertion error created by fastify can be safely ignore
367
367
  // because it only duplicate route for head
368
368
  if (!context[kRouteByFastify]) {
369
- const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route '${opts.url}'`)
369
+ const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route`)
370
370
  if (isDuplicatedRoute) {
371
371
  throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
372
372
  }
@@ -407,7 +407,7 @@ function buildRouting (options) {
407
407
  fourOhFour.setContext(this, context)
408
408
 
409
409
  if (opts.schema) {
410
- context.schema = normalizeSchema(context.schema, this.initialConfig)
410
+ context.schema = normalizeSchema(opts, context.schema, this.initialConfig)
411
411
 
412
412
  const schemaController = this[kSchemaController]
413
413
  if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) {
package/lib/schemas.js CHANGED
@@ -4,6 +4,9 @@ const fastClone = require('rfdc')({ circles: false, proto: true })
4
4
  const { kSchemaVisited, kSchemaResponse } = require('./symbols')
5
5
  const kFluentSchema = Symbol.for('fluent-schema-object')
6
6
 
7
+ const {
8
+ FSTDEP022
9
+ } = require('./warnings')
7
10
  const {
8
11
  FST_ERR_SCH_MISSING_ID,
9
12
  FST_ERR_SCH_ALREADY_PRESENT,
@@ -54,7 +57,7 @@ function isCustomSchemaPrototype (schema) {
54
57
  return typeof schema === 'object' && Object.getPrototypeOf(schema) !== Object.prototype
55
58
  }
56
59
 
57
- function normalizeSchema (routeSchemas, serverOptions) {
60
+ function normalizeSchema (opts, routeSchemas, serverOptions) {
58
61
  if (routeSchemas[kSchemaVisited]) {
59
62
  return routeSchemas
60
63
  }
@@ -73,7 +76,20 @@ function normalizeSchema (routeSchemas, serverOptions) {
73
76
  for (const key of SCHEMAS_SOURCE) {
74
77
  const schema = routeSchemas[key]
75
78
  if (schema && !isCustomSchemaPrototype(schema)) {
76
- routeSchemas[key] = getSchemaAnyway(schema, serverOptions.jsonShorthand)
79
+ if (key === 'body' && schema.content) {
80
+ const contentProperty = schema.content
81
+ const keys = Object.keys(contentProperty)
82
+ for (let i = 0; i < keys.length; i++) {
83
+ const contentType = keys[i]
84
+ const contentSchema = contentProperty[contentType].schema
85
+ if (!contentSchema) {
86
+ throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(contentType)
87
+ }
88
+ routeSchemas.body.content[contentType].schema = getSchemaAnyway(opts.url, contentSchema, serverOptions.jsonShorthand)
89
+ }
90
+ continue
91
+ }
92
+ routeSchemas[key] = getSchemaAnyway(opts.url, schema, serverOptions.jsonShorthand)
77
93
  }
78
94
  }
79
95
 
@@ -95,7 +111,7 @@ function normalizeSchema (routeSchemas, serverOptions) {
95
111
  if (keys.length === 1) { break }
96
112
  throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(mediaName)
97
113
  }
98
- routeSchemas.response[code].content[mediaName].schema = getSchemaAnyway(contentProperty[mediaName].schema, serverOptions.jsonShorthand)
114
+ routeSchemas.response[code].content[mediaName].schema = getSchemaAnyway(opts.url, contentProperty[mediaName].schema, serverOptions.jsonShorthand)
99
115
  if (i === keys.length - 1) {
100
116
  hasContentMultipleContentTypes = true
101
117
  }
@@ -103,7 +119,7 @@ function normalizeSchema (routeSchemas, serverOptions) {
103
119
  }
104
120
 
105
121
  if (!hasContentMultipleContentTypes) {
106
- routeSchemas.response[code] = getSchemaAnyway(routeSchemas.response[code], serverOptions.jsonShorthand)
122
+ routeSchemas.response[code] = getSchemaAnyway(opts.url, routeSchemas.response[code], serverOptions.jsonShorthand)
107
123
  }
108
124
  }
109
125
  }
@@ -129,9 +145,10 @@ function generateFluentSchema (schema) {
129
145
  }
130
146
  }
131
147
 
132
- function getSchemaAnyway (schema, jsonShorthand) {
148
+ function getSchemaAnyway (url, schema, jsonShorthand) {
133
149
  if (!jsonShorthand || schema.$ref || schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema
134
150
  if (!schema.type && !schema.properties) {
151
+ FSTDEP022(url)
135
152
  return {
136
153
  type: 'object',
137
154
  properties: schema
package/lib/server.js CHANGED
@@ -72,7 +72,8 @@ function createServer (options, httpHandler) {
72
72
  } else {
73
73
  host = listenOptions.host
74
74
  }
75
- if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false) {
75
+ if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false ||
76
+ listenOptions.host == null) {
76
77
  listenOptions.host = host
77
78
  }
78
79
  if (host === 'localhost') {
package/lib/validation.js CHANGED
@@ -87,7 +87,17 @@ function compileSchemasForValidation (context, compile, isCustom) {
87
87
  }
88
88
 
89
89
  if (schema.body) {
90
- context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' })
90
+ const contentProperty = schema.body.content
91
+ if (contentProperty) {
92
+ const contentTypeSchemas = {}
93
+ for (const contentType of Object.keys(contentProperty)) {
94
+ const contentSchema = contentProperty[contentType].schema
95
+ contentTypeSchemas[contentType] = compile({ schema: contentSchema, method, url, httpPart: 'body', contentType })
96
+ }
97
+ context[bodySchema] = contentTypeSchemas
98
+ } else {
99
+ context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' })
100
+ }
91
101
  } else if (Object.prototype.hasOwnProperty.call(schema, 'body')) {
92
102
  FSTWRN001('body', method, url)
93
103
  }
@@ -140,7 +150,18 @@ function validate (context, request, execution) {
140
150
  }
141
151
 
142
152
  if (runExecution || !execution.skipBody) {
143
- const body = validateParam(context[bodySchema], request, 'body')
153
+ let validatorFunction = null
154
+ if (typeof context[bodySchema] === 'function') {
155
+ validatorFunction = context[bodySchema]
156
+ } else if (context[bodySchema]) {
157
+ // TODO: add request.contentType and reuse it here
158
+ const contentType = getEssenceMediaType(request.headers['content-type'])
159
+ const contentSchema = context[bodySchema][contentType]
160
+ if (contentSchema) {
161
+ validatorFunction = contentSchema
162
+ }
163
+ }
164
+ const body = validateParam(validatorFunction, request, 'body')
144
165
  if (body) {
145
166
  if (typeof body.then !== 'function') {
146
167
  return wrapValidationError(body, 'body', context.schemaErrorFormatter)
@@ -233,6 +254,16 @@ function wrapValidationError (result, dataVar, schemaErrorFormatter) {
233
254
  return error
234
255
  }
235
256
 
257
+ /**
258
+ * simple function to retrieve the essence media type
259
+ * @param {string} header
260
+ * @returns {string} Mimetype string.
261
+ */
262
+ function getEssenceMediaType (header) {
263
+ if (!header) return ''
264
+ return header.split(/[ ;]/, 1)[0].trim().toLowerCase()
265
+ }
266
+
236
267
  module.exports = {
237
268
  symbols: { bodySchema, querystringSchema, responseSchema, paramsSchema, headersSchema },
238
269
  compileSchemasForValidation,
package/lib/warnings.js CHANGED
@@ -87,6 +87,11 @@ const FSTDEP021 = createDeprecation({
87
87
  message: 'The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'
88
88
  })
89
89
 
90
+ const FSTDEP022 = createDeprecation({
91
+ code: 'FSTDEP021',
92
+ message: 'You are using the deprecated json shorthand schema on route %s. Specify full object schema instead. It will be removed in `fastify@v5`'
93
+ })
94
+
90
95
  const FSTWRN001 = createWarning({
91
96
  name: 'FastifyWarning',
92
97
  code: 'FSTWRN001',
@@ -119,6 +124,7 @@ module.exports = {
119
124
  FSTDEP019,
120
125
  FSTDEP020,
121
126
  FSTDEP021,
127
+ FSTDEP022,
122
128
  FSTWRN001,
123
129
  FSTWRN002
124
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "4.28.1",
3
+ "version": "4.29.1",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -10,11 +10,14 @@ const split = require('split2')
10
10
  const { sleep } = require('./helper')
11
11
 
12
12
  test('close callback', t => {
13
- t.plan(4)
13
+ t.plan(7)
14
14
  const fastify = Fastify()
15
15
  fastify.addHook('onClose', onClose)
16
16
  function onClose (instance, done) {
17
+ t.type(fastify, this)
17
18
  t.type(fastify, instance)
19
+ t.equal(fastify, this)
20
+ t.equal(fastify, instance)
18
21
  done()
19
22
  }
20
23
 
package/test/copy.test.js CHANGED
@@ -12,7 +12,10 @@ test('can be created - copy', t => {
12
12
  method: 'COPY',
13
13
  url: '*',
14
14
  handler: function (req, reply) {
15
- reply.code(204).send()
15
+ reply.code(204)
16
+ .header('location', req.headers.destination)
17
+ .header('body', req.body.toString())
18
+ .send()
16
19
  }
17
20
  })
18
21
  t.pass()
@@ -26,15 +29,19 @@ fastify.listen({ port: 0 }, err => {
26
29
  t.teardown(() => { fastify.close() })
27
30
 
28
31
  test('request - copy', t => {
29
- t.plan(2)
32
+ t.plan(4)
30
33
  sget({
31
34
  url: `http://localhost:${fastify.server.address().port}/test.txt`,
32
35
  method: 'COPY',
33
36
  headers: {
34
- Destination: '/test2.txt'
35
- }
36
- }, (err, response, body) => {
37
+ destination: '/test2.txt',
38
+ 'Content-Type': 'text/plain'
39
+ },
40
+ body: '/test3.txt'
41
+ }, (err, response) => {
37
42
  t.error(err)
43
+ t.equal(response.headers.location, '/test2.txt')
44
+ t.equal(response.headers.body, '/test3.txt')
38
45
  t.equal(response.statusCode, 204)
39
46
  })
40
47
  })
@@ -63,7 +63,7 @@ test('build schema - output schema', t => {
63
63
  t.equal(typeof opts[symbols.responseSchema]['201'], 'function')
64
64
  })
65
65
 
66
- test('build schema - payload schema', t => {
66
+ test('build schema - body schema', t => {
67
67
  t.plan(1)
68
68
  const opts = {
69
69
  schema: {
@@ -79,6 +79,32 @@ test('build schema - payload schema', t => {
79
79
  t.equal(typeof opts[symbols.bodySchema], 'function')
80
80
  })
81
81
 
82
+ test('build schema - body with multiple content type schemas', t => {
83
+ t.plan(2)
84
+ const opts = {
85
+ schema: {
86
+ body: {
87
+ content: {
88
+ 'application/json': {
89
+ schema: {
90
+ type: 'object',
91
+ properties: {
92
+ hello: { type: 'string' }
93
+ }
94
+ }
95
+ },
96
+ 'text/plain': {
97
+ schema: { type: 'string' }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
104
+ t.type(opts[symbols.bodySchema]['application/json'], 'function')
105
+ t.type(opts[symbols.bodySchema]['text/plain'], 'function')
106
+ })
107
+
82
108
  test('build schema - avoid repeated normalize schema', t => {
83
109
  t.plan(3)
84
110
  const serverConfig = {
@@ -94,10 +120,10 @@ test('build schema - avoid repeated normalize schema', t => {
94
120
  }
95
121
  }
96
122
  }
97
- opts.schema = normalizeSchema(opts.schema, serverConfig)
123
+ opts.schema = normalizeSchema({}, opts.schema, serverConfig)
98
124
  t.not(kSchemaVisited, undefined)
99
125
  t.equal(opts.schema[kSchemaVisited], true)
100
- t.equal(opts.schema, normalizeSchema(opts.schema, serverConfig))
126
+ t.equal(opts.schema, normalizeSchema({}, opts.schema, serverConfig))
101
127
  })
102
128
 
103
129
  test('build schema - query schema', t => {
@@ -115,7 +141,7 @@ test('build schema - query schema', t => {
115
141
  }
116
142
  }
117
143
  }
118
- opts.schema = normalizeSchema(opts.schema, serverConfig)
144
+ opts.schema = normalizeSchema({}, opts.schema, serverConfig)
119
145
  validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
120
146
  t.type(opts[symbols.querystringSchema].schema.type, 'string')
121
147
  t.equal(typeof opts[symbols.querystringSchema], 'function')
@@ -133,7 +159,7 @@ test('build schema - query schema abbreviated', t => {
133
159
  }
134
160
  }
135
161
  }
136
- opts.schema = normalizeSchema(opts.schema, serverConfig)
162
+ opts.schema = normalizeSchema({}, opts.schema, serverConfig)
137
163
  validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
138
164
  t.type(opts[symbols.querystringSchema].schema.type, 'string')
139
165
  t.equal(typeof opts[symbols.querystringSchema], 'function')
@@ -168,7 +194,7 @@ test('build schema - querystring schema abbreviated', t => {
168
194
  }
169
195
  }
170
196
  }
171
- opts.schema = normalizeSchema(opts.schema, serverConfig)
197
+ opts.schema = normalizeSchema({}, opts.schema, serverConfig)
172
198
  validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema))
173
199
  t.type(opts[symbols.querystringSchema].schema.type, 'string')
174
200
  t.equal(typeof opts[symbols.querystringSchema], 'function')
@@ -196,7 +222,7 @@ test('build schema - must throw if querystring and query schema exist', t => {
196
222
  }
197
223
  }
198
224
  }
199
- opts.schema = normalizeSchema(opts.schema, serverConfig)
225
+ opts.schema = normalizeSchema({}, opts.schema, serverConfig)
200
226
  } catch (err) {
201
227
  t.equal(err.code, 'FST_ERR_SCH_DUPLICATE')
202
228
  t.equal(err.message, 'Schema with \'querystring\' already present!')
@@ -354,7 +380,7 @@ test('build schema - mixed schema types are individually skipped or normalized',
354
380
  }]
355
381
 
356
382
  testCases.forEach((testCase) => {
357
- const result = normalizeSchema(testCase.schema, { jsonShorthand: true })
383
+ const result = normalizeSchema({}, testCase.schema, { jsonShorthand: true })
358
384
  testCase.assertions(result)
359
385
  })
360
386
  })