fastify 3.20.2 → 3.21.3

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 (46) hide show
  1. package/.taprc +1 -0
  2. package/README.md +30 -1
  3. package/docs/ContentTypeParser.md +2 -2
  4. package/docs/Ecosystem.md +3 -1
  5. package/docs/Getting-Started.md +76 -3
  6. package/docs/Guides/Index.md +6 -0
  7. package/docs/Hooks.md +42 -0
  8. package/docs/Lifecycle.md +1 -1
  9. package/docs/Recommendations.md +80 -1
  10. package/docs/Reply.md +2 -2
  11. package/docs/Request.md +4 -4
  12. package/docs/Server.md +82 -16
  13. package/docs/Validation-and-Serialization.md +47 -4
  14. package/fastify.js +11 -0
  15. package/lib/contentTypeParser.js +3 -5
  16. package/lib/decorate.js +33 -9
  17. package/lib/reply.js +17 -1
  18. package/lib/request.js +8 -0
  19. package/lib/route.js +4 -3
  20. package/lib/schema-controller.js +12 -7
  21. package/package.json +6 -4
  22. package/test/bundler/README.md +29 -0
  23. package/test/bundler/webpack/bundler-test.js +24 -0
  24. package/test/bundler/webpack/package.json +11 -0
  25. package/test/bundler/webpack/src/fail-plugin-version.js +14 -0
  26. package/test/bundler/webpack/src/index.js +9 -0
  27. package/test/bundler/webpack/webpack.config.js +13 -0
  28. package/test/custom-parser.test.js +111 -0
  29. package/test/decorator.test.js +27 -0
  30. package/test/diagnostics-channel.test.js +61 -0
  31. package/test/internals/decorator.test.js +1 -1
  32. package/test/reply-error.test.js +71 -0
  33. package/test/route.test.js +22 -0
  34. package/test/same-shape.test.js +124 -0
  35. package/test/schema-feature.test.js +1 -1
  36. package/test/schema-serialization.test.js +41 -0
  37. package/test/schema-special-usage.test.js +43 -2
  38. package/test/schema-validation.test.js +100 -0
  39. package/test/stream.test.js +37 -1
  40. package/test/types/instance.test-d.ts +10 -2
  41. package/test/types/plugin.test-d.ts +1 -1
  42. package/test/types/request.test-d.ts +4 -1
  43. package/types/.eslintrc.json +2 -1
  44. package/types/instance.d.ts +4 -3
  45. package/types/request.d.ts +1 -1
  46. package/types/schema.d.ts +1 -1
@@ -14,7 +14,7 @@ Fastify uses a schema-based approach, and even if it is not mandatory we recomme
14
14
 
15
15
  ### Core concepts
16
16
  The validation and the serialization tasks are processed by two different, and customizable, actors:
17
- - [Ajv](https://www.npmjs.com/package/ajv) for the validation of a request
17
+ - [Ajv v6](https://www.npmjs.com/package/ajv/v/6.12.6) for the validation of a request
18
18
  - [fast-json-stringify](https://www.npmjs.com/package/fast-json-stringify) for the serialization of a response's body
19
19
 
20
20
  These two separate entities share only the JSON schemas added to Fastify's instance through `.addSchema(schema)`.
@@ -120,7 +120,7 @@ fastify.register((instance, opts, done) => {
120
120
 
121
121
 
122
122
  ### Validation
123
- The route validation internally relies upon [Ajv](https://www.npmjs.com/package/ajv), which is a high-performance JSON Schema validator.
123
+ The route validation internally relies upon [Ajv v6](https://www.npmjs.com/package/ajv/v/6.12.6) which is a high-performance JSON Schema validator.
124
124
  Validating the input is very easy: just add the fields that you need inside the route schema, and you are done!
125
125
 
126
126
  The supported validations are:
@@ -131,6 +131,8 @@ The supported validations are:
131
131
 
132
132
  All the validations can be a complete JSON Schema object (with a `type` property of `'object'` and a `'properties'` object containing parameters) or a simpler variation in which the `type` and `properties` attributes are forgone and the parameters are listed at the top level (see the example below).
133
133
 
134
+ > ℹ If you need to use the lastest version of Ajv (v8) you should read how to do it in the [`schemaController`](Server.md#schema-controller) section. It is explained the easier way to avoid to implement a custom validator.
135
+
134
136
  Example:
135
137
  ```js
136
138
  const bodyJsonSchema = {
@@ -232,7 +234,7 @@ curl -X GET "http://localhost:3000/?ids=1
232
234
  {"statusCode":400,"error":"Bad Request","message":"querystring/hello should be array"}
233
235
  ```
234
236
 
235
- Using `coerceTypes` as 'array' should fix it:
237
+ Using `coerceTypes` as 'array' will fix it:
236
238
 
237
239
  ```js
238
240
  const ajv = new Ajv({
@@ -253,12 +255,53 @@ curl -X GET "http://localhost:3000/?ids=1
253
255
  {"params":{"hello":["1"]}}
254
256
  ```
255
257
 
258
+ You can also specify a custom schema validator for each parameter type (body, querystring, params, headers).
259
+
260
+ For example, the following code disable type cohercion only for the `body` parameters, changing the ajv default options:
261
+
262
+ ```js
263
+ const schemaCompilers = {
264
+ body: new Ajv({
265
+ removeAdditional: false,
266
+ coerceTypes: false,
267
+ allErrors: true
268
+ }),
269
+ params: new Ajv({
270
+ removeAdditional: false,
271
+ coerceTypes: true,
272
+ allErrors: true
273
+ }),
274
+ querystring: new Ajv({
275
+ removeAdditional: false,
276
+ coerceTypes: true,
277
+ allErrors: true
278
+ }),
279
+ headers: new Ajv({
280
+ removeAdditional: false,
281
+ coerceTypes: true,
282
+ allErrors: true
283
+ })
284
+ }
285
+
286
+ server.setValidatorCompiler(req => {
287
+ if (!req.httpPart) {
288
+ throw new Error('Missing httpPart')
289
+ }
290
+ const compiler = schemaCompilers[req.httpPart]
291
+ if (!compiler) {
292
+ throw new Error(`Missing compiler for ${req.httpPart}`)
293
+ }
294
+ return compiler.compile(req.schema)
295
+ })
296
+ ```
297
+
256
298
  For further information see [here](https://ajv.js.org/coercion.html)
257
299
 
258
300
  <a name="ajv-plugins"></a>
259
301
  #### Ajv Plugins
260
302
 
261
- You can provide a list of plugins you want to use with Ajv:
303
+ You can provide a list of plugins you want to use with the default `ajv` instance.
304
+ Note that the plugin must be **compatible with Ajv v6**.
262
305
 
263
306
  > Refer to [`ajv options`](Server.md#ajv) to check plugins format
264
307
 
package/fastify.js CHANGED
@@ -399,6 +399,17 @@ function fastify (options) {
399
399
  // Delay configuring clientError handler so that it can access fastify state.
400
400
  server.on('clientError', options.clientErrorHandler.bind(fastify))
401
401
 
402
+ try {
403
+ const dc = require('diagnostics_channel')
404
+ const initChannel = dc.channel('fastify.initialization')
405
+ if (initChannel.hasSubscribers) {
406
+ initChannel.publish({ fastify })
407
+ }
408
+ } catch (e) {
409
+ // This only happens if `diagnostics_channel` isn't available, i.e. earlier
410
+ // versions of Node.js. In that event, we don't care, so ignore the error.
411
+ }
412
+
402
413
  return fastify
403
414
 
404
415
  function throwIfAlreadyStarted (msg) {
@@ -67,9 +67,7 @@ ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
67
67
  this.customParsers[''] = parser
68
68
  } else {
69
69
  if (contentTypeIsString) {
70
- if (contentType !== 'application/json' && contentType !== 'text/plain') {
71
- this.parserList.unshift(contentType)
72
- }
70
+ this.parserList.unshift(contentType)
73
71
  } else {
74
72
  this.parserRegExpList.unshift(contentType)
75
73
  }
@@ -83,10 +81,10 @@ ContentTypeParser.prototype.hasParser = function (contentType) {
83
81
 
84
82
  ContentTypeParser.prototype.existingParser = function (contentType) {
85
83
  if (contentType === 'application/json') {
86
- return this.customParsers['application/json'].fn !== this[kDefaultJsonParse]
84
+ return this.customParsers['application/json'] && this.customParsers['application/json'].fn !== this[kDefaultJsonParse]
87
85
  }
88
86
  if (contentType === 'text/plain') {
89
- return this.customParsers['text/plain'].fn !== defaultPlainTextParser
87
+ return this.customParsers['text/plain'] && this.customParsers['text/plain'].fn !== defaultPlainTextParser
90
88
  }
91
89
 
92
90
  return contentType in this.customParsers
package/lib/decorate.js CHANGED
@@ -22,21 +22,35 @@ function decorate (instance, name, fn, dependencies) {
22
22
  throw new FST_ERR_DEC_ALREADY_PRESENT(name)
23
23
  }
24
24
 
25
- if (dependencies) {
26
- if (!Array.isArray(dependencies)) {
27
- throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
28
- }
25
+ checkDependencies(instance, name, dependencies)
29
26
 
30
- checkDependencies(instance, dependencies)
27
+ if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
28
+ Object.defineProperty(instance, name, {
29
+ get: fn.getter,
30
+ set: fn.setter
31
+ })
32
+ } else {
33
+ instance[name] = fn
31
34
  }
35
+ }
36
+
37
+ function decorateConstructor (konstructor, name, fn, dependencies) {
38
+ const instance = konstructor.prototype
39
+ if (instance.hasOwnProperty(name) || konstructor.props.includes(name)) {
40
+ throw new FST_ERR_DEC_ALREADY_PRESENT(name)
41
+ }
42
+
43
+ checkDependencies(instance, name, dependencies)
32
44
 
33
45
  if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
34
46
  Object.defineProperty(instance, name, {
35
47
  get: fn.getter,
36
48
  set: fn.setter
37
49
  })
38
- } else {
50
+ } else if (fn) {
39
51
  instance[name] = fn
52
+ } else {
53
+ konstructor.props.push(name)
40
54
  }
41
55
  }
42
56
 
@@ -61,14 +75,24 @@ function checkExistence (instance, name) {
61
75
  }
62
76
 
63
77
  function checkRequestExistence (name) {
78
+ if (name && this[kRequest].props.includes(name)) return true
64
79
  return checkExistence(this[kRequest].prototype, name)
65
80
  }
66
81
 
67
82
  function checkReplyExistence (name) {
83
+ if (name && this[kReply].props.includes(name)) return true
68
84
  return checkExistence(this[kReply].prototype, name)
69
85
  }
70
86
 
71
- function checkDependencies (instance, deps) {
87
+ function checkDependencies (instance, name, deps) {
88
+ if (deps === undefined || deps === null) {
89
+ return
90
+ }
91
+
92
+ if (!Array.isArray(deps)) {
93
+ throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
94
+ }
95
+
72
96
  // eslint-disable-next-line no-var
73
97
  for (var i = 0; i !== deps.length; ++i) {
74
98
  if (!checkExistence(instance, deps[i])) {
@@ -80,14 +104,14 @@ function checkDependencies (instance, deps) {
80
104
  function decorateReply (name, fn, dependencies) {
81
105
  assertNotStarted(this, name)
82
106
  checkReferenceType(name, fn)
83
- decorate(this[kReply].prototype, name, fn, dependencies)
107
+ decorateConstructor(this[kReply], name, fn, dependencies)
84
108
  return this
85
109
  }
86
110
 
87
111
  function decorateRequest (name, fn, dependencies) {
88
112
  assertNotStarted(this, name)
89
113
  checkReferenceType(name, fn)
90
- decorate(this[kRequest].prototype, name, fn, dependencies)
114
+ decorateConstructor(this[kRequest], name, fn, dependencies)
91
115
  return this
92
116
  }
93
117
 
package/lib/reply.js CHANGED
@@ -63,6 +63,7 @@ function Reply (res, request, log) {
63
63
  this[kReplyStartTime] = undefined
64
64
  this.log = log
65
65
  }
66
+ Reply.props = []
66
67
 
67
68
  Object.defineProperties(Reply.prototype, {
68
69
  context: {
@@ -377,8 +378,8 @@ function preserializeHookEnd (err, request, reply, payload) {
377
378
  }
378
379
 
379
380
  function onSendHook (reply, payload) {
380
- reply[kReplySent] = true
381
381
  if (reply.context.onSend !== null) {
382
+ reply[kReplySent] = true
382
383
  onSendHookRunner(
383
384
  reply.context.onSend,
384
385
  reply.request,
@@ -422,6 +423,8 @@ function onSendEnd (reply, payload) {
422
423
  }
423
424
 
424
425
  if (typeof payload.pipe === 'function') {
426
+ reply[kReplySent] = true
427
+
425
428
  sendStream(payload, res, reply)
426
429
  return
427
430
  }
@@ -573,12 +576,17 @@ function handleError (reply, error, cb) {
573
576
  message: { value: error.message || '' },
574
577
  statusCode: { value: statusCode }
575
578
  }))
579
+
580
+ if (serializerFn !== false && typeof payload !== 'string') {
581
+ throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
582
+ }
576
583
  } catch (err) {
577
584
  // error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
578
585
  reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed')
579
586
  res.statusCode = 500
580
587
  payload = serializeError({
581
588
  error: statusCodes['500'],
589
+ code: err.code,
582
590
  message: err.message,
583
591
  statusCode: 500
584
592
  })
@@ -651,6 +659,8 @@ function onResponseCallback (err, request, reply) {
651
659
  }
652
660
 
653
661
  function buildReply (R) {
662
+ const props = [...R.props]
663
+
654
664
  function _Reply (res, request, log) {
655
665
  this.raw = res
656
666
  this[kReplyIsError] = false
@@ -662,8 +672,14 @@ function buildReply (R) {
662
672
  this[kReplyHeaders] = {}
663
673
  this[kReplyStartTime] = undefined
664
674
  this.log = log
675
+
676
+ // eslint-disable-next-line no-var
677
+ for (var i = 0; i < props.length; i++) {
678
+ this[props[i]] = null
679
+ }
665
680
  }
666
681
  _Reply.prototype = new R()
682
+ _Reply.props = props
667
683
  return _Reply
668
684
  }
669
685
 
package/lib/request.js CHANGED
@@ -13,6 +13,7 @@ function Request (id, params, req, query, log, context) {
13
13
  this.log = log
14
14
  this.body = null
15
15
  }
16
+ Request.props = []
16
17
 
17
18
  function getTrustProxyFn (tp) {
18
19
  if (typeof tp === 'function') {
@@ -43,6 +44,7 @@ function buildRequest (R, trustProxy) {
43
44
  }
44
45
 
45
46
  function buildRegularRequest (R) {
47
+ const props = [...R.props]
46
48
  function _Request (id, params, req, query, log, context) {
47
49
  this.id = id
48
50
  this.context = context
@@ -51,8 +53,14 @@ function buildRegularRequest (R) {
51
53
  this.query = query
52
54
  this.log = log
53
55
  this.body = null
56
+
57
+ // eslint-disable-next-line no-var
58
+ for (var i = 0; i < props.length; i++) {
59
+ this[props[i]] = null
60
+ }
54
61
  }
55
62
  _Request.prototype = new R()
63
+ _Request.props = props
56
64
 
57
65
  return _Request
58
66
  }
package/lib/route.js CHANGED
@@ -143,12 +143,14 @@ function buildRouting (options) {
143
143
  }
144
144
  }
145
145
 
146
+ const path = opts.url || opts.path
147
+
146
148
  if (!opts.handler) {
147
- throw new Error(`Missing handler function for ${opts.method}:${opts.url} route.`)
149
+ throw new Error(`Missing handler function for ${opts.method}:${path} route.`)
148
150
  }
149
151
 
150
152
  if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') {
151
- throw new Error(`Error Handler for ${opts.method}:${opts.url} route, if defined, must be a function`)
153
+ throw new Error(`Error Handler for ${opts.method}:${path} route, if defined, must be a function`)
152
154
  }
153
155
 
154
156
  validateBodyLimitOption(opts.bodyLimit)
@@ -156,7 +158,6 @@ function buildRouting (options) {
156
158
  const prefix = this[kRoutePrefix]
157
159
 
158
160
  this.after((notHandledErr, done) => {
159
- const path = opts.url || opts.path
160
161
  if (path === '/' && prefix.length && opts.method !== 'HEAD') {
161
162
  switch (opts.prefixTrailingSlash) {
162
163
  case 'slash':
@@ -15,13 +15,18 @@ function buildSchemaController (parentSchemaCtrl, opts) {
15
15
  return new SchemaController(parentSchemaCtrl, opts)
16
16
  }
17
17
 
18
- const option = Object.assign({
19
- bucket: buildSchemas,
20
- compilersFactory: {
21
- buildValidator: ValidatorSelector(),
22
- buildSerializer: serializerCompiler
23
- }
24
- }, opts)
18
+ let compilersFactory = {
19
+ buildValidator: ValidatorSelector(),
20
+ buildSerializer: serializerCompiler
21
+ }
22
+ if (opts && opts.compilersFactory) {
23
+ compilersFactory = Object.assign(compilersFactory, opts.compilersFactory)
24
+ }
25
+
26
+ const option = {
27
+ bucket: (opts && opts.bucket) || buildSchemas,
28
+ compilersFactory: compilersFactory
29
+ }
25
30
 
26
31
  return new SchemaController(undefined, option)
27
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "3.20.2",
3
+ "version": "3.21.3",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "type": "commonjs",
@@ -115,7 +115,7 @@
115
115
  },
116
116
  "homepage": "https://www.fastify.io/",
117
117
  "devDependencies": {
118
- "@fastify/ajv-compiler-8": "github:fastify/ajv-compiler#ajv-8",
118
+ "@fastify/ajv-compiler-8": "npm:@fastify/ajv-compiler@^2.0.0",
119
119
  "@fastify/pre-commit": "^2.0.1",
120
120
  "@hapi/joi": "^17.1.1",
121
121
  "@sinonjs/fake-timers": "^7.0.0",
@@ -123,9 +123,9 @@
123
123
  "@types/pino": "^6.0.1",
124
124
  "@typescript-eslint/eslint-plugin": "^4.5.0",
125
125
  "@typescript-eslint/parser": "^4.5.0",
126
- "JSONStream": "^1.3.5",
127
126
  "ajv": "^6.0.0",
128
127
  "ajv-errors": "^1.0.1",
128
+ "ajv-formats": "^2.1.1",
129
129
  "ajv-i18n": "^3.5.0",
130
130
  "ajv-merge-patch": "^4.1.0",
131
131
  "ajv-pack": "^0.3.1",
@@ -151,6 +151,7 @@
151
151
  "hsts": "^2.2.0",
152
152
  "http-errors": "^1.7.1",
153
153
  "ienoopen": "^1.1.0",
154
+ "JSONStream": "^1.3.5",
154
155
  "license-checker": "^25.0.1",
155
156
  "pem": "^1.14.4",
156
157
  "proxyquire": "^2.1.3",
@@ -193,7 +194,8 @@
193
194
  "lib/configValidator.js",
194
195
  "fastify.d.ts",
195
196
  "types/*",
196
- "test/types/*"
197
+ "test/types/*",
198
+ "test/same-shape.test.js"
197
199
  ]
198
200
  },
199
201
  "tsd": {
@@ -0,0 +1,29 @@
1
+ # Bundlers test stack
2
+
3
+ In some cases developers bundle their apps for several targets, eg: serveless applications.
4
+ Even if it's not recommended by Fastify team; we need to ensure we do not break the build process.
5
+ Please note this might result in feature behaving differently like the version handling check for plugins.
6
+
7
+ ## Test bundlers
8
+
9
+ The bundler test stack has been set appart than the rest of the Unit testing stack because it's not a
10
+ part of the fastify lib itself. Note that the tests run in CI only on NodeJs LTS version.
11
+ Developers does not need to install every bundler to run unit tests.
12
+
13
+ To run the bundler tests you'll need to first install the repository dependencies and after the bundler
14
+ stack dependencies. See:
15
+
16
+ ```bash
17
+ # path: root of repository /fastify
18
+ npm i
19
+ cd test/bundler/webpack
20
+ npm i
21
+ npm run test # test command runs bundle before of starting the test
22
+ ```
23
+
24
+ ## Bundler test development
25
+
26
+ To not break the fastify unit testing stack please name test files like this `*-test.js` and not `*.test.js`,
27
+ otherwise it can be catched by unit-test regex of fastify.
28
+ Test need to ensure the build process works and the fastify application can be run,
29
+ no need to go in deep testing unless an issue is raised.
@@ -0,0 +1,24 @@
1
+ 'use strict'
2
+
3
+ const t = require('tap')
4
+ const test = t.test
5
+ const fastifySuccess = require('./dist/success')
6
+ const fastifyFailPlugin = require('./dist/failPlugin')
7
+
8
+ test('Bundled package should work', t => {
9
+ t.plan(1)
10
+ fastifySuccess.ready((err) => {
11
+ t.error(err)
12
+ })
13
+ })
14
+
15
+ // In the webpack bundle context the fastify package.json is not read
16
+ // Because of this the version is set to `undefined`, this makes the plugin
17
+ // version check not able to work properly. By then this test shouldn't work
18
+ // in non-bundled environment but works in bundled environment
19
+ test('Bundled package should work with bad plugin version, undefined version fallback', t => {
20
+ t.plan(1)
21
+ fastifyFailPlugin.ready((err) => {
22
+ t.error(err)
23
+ })
24
+ })
@@ -0,0 +1,11 @@
1
+ {
2
+ "version":"0.0.1",
3
+ "scripts": {
4
+ "bundle": "webpack",
5
+ "test": "npm run bundle && node bundler-test.js"
6
+ },
7
+ "devDependencies": {
8
+ "webpack": "^5.49.0",
9
+ "webpack-cli": "^4.7.2"
10
+ }
11
+ }
@@ -0,0 +1,14 @@
1
+ const fp = require('fastify-plugin')
2
+ const fastify = require('../../../../')({
3
+ logger: true
4
+ })
5
+
6
+ fastify.get('/', function (request, reply) {
7
+ reply.send({ hello: 'world' })
8
+ })
9
+
10
+ fastify.register(fp((instance, opts, done) => {
11
+ done()
12
+ }, { fastify: '9.x' }))
13
+
14
+ module.exports = fastify
@@ -0,0 +1,9 @@
1
+ const fastify = require('../../../../')({
2
+ logger: true
3
+ })
4
+ // Declare a route
5
+ fastify.get('/', function (request, reply) {
6
+ reply.send({ hello: 'world' })
7
+ })
8
+
9
+ module.exports = fastify
@@ -0,0 +1,13 @@
1
+ const path = require('path')
2
+
3
+ module.exports = {
4
+ entry: { success: './src/index.js', failPlugin: './src/fail-plugin-version.js' },
5
+ target: 'node',
6
+ output: {
7
+ path: path.resolve(__dirname, 'dist'),
8
+ filename: '[name].js',
9
+ library: {
10
+ type: 'commonjs2'
11
+ }
12
+ }
13
+ }
@@ -1707,3 +1707,114 @@ test('cannot remove content type parsers after binding', t => {
1707
1707
  t.throws(() => fastify.removeContentTypeParser('application/json'))
1708
1708
  })
1709
1709
  })
1710
+
1711
+ test('should be able to override the default json parser after removeAllContentTypeParsers', t => {
1712
+ t.plan(5)
1713
+
1714
+ const fastify = Fastify()
1715
+
1716
+ fastify.post('/', (req, reply) => {
1717
+ reply.send(req.body)
1718
+ })
1719
+
1720
+ fastify.removeAllContentTypeParsers()
1721
+
1722
+ fastify.addContentTypeParser('application/json', function (req, payload, done) {
1723
+ t.ok('called')
1724
+ jsonParser(payload, function (err, body) {
1725
+ done(err, body)
1726
+ })
1727
+ })
1728
+
1729
+ fastify.listen(0, err => {
1730
+ t.error(err)
1731
+
1732
+ sget({
1733
+ method: 'POST',
1734
+ url: 'http://localhost:' + fastify.server.address().port,
1735
+ body: '{"hello":"world"}',
1736
+ headers: {
1737
+ 'Content-Type': 'application/json'
1738
+ }
1739
+ }, (err, response, body) => {
1740
+ t.error(err)
1741
+ t.equal(response.statusCode, 200)
1742
+ t.same(body.toString(), JSON.stringify({ hello: 'world' }))
1743
+ fastify.close()
1744
+ })
1745
+ })
1746
+ })
1747
+
1748
+ test('should be able to override the default plain text parser after removeAllContentTypeParsers', t => {
1749
+ t.plan(5)
1750
+
1751
+ const fastify = Fastify()
1752
+
1753
+ fastify.post('/', (req, reply) => {
1754
+ reply.send(req.body)
1755
+ })
1756
+
1757
+ fastify.removeAllContentTypeParsers()
1758
+
1759
+ fastify.addContentTypeParser('text/plain', function (req, payload, done) {
1760
+ t.ok('called')
1761
+ plainTextParser(payload, function (err, body) {
1762
+ done(err, body)
1763
+ })
1764
+ })
1765
+
1766
+ fastify.listen(0, err => {
1767
+ t.error(err)
1768
+
1769
+ sget({
1770
+ method: 'POST',
1771
+ url: 'http://localhost:' + fastify.server.address().port,
1772
+ body: 'hello world',
1773
+ headers: {
1774
+ 'Content-Type': 'text/plain'
1775
+ }
1776
+ }, (err, response, body) => {
1777
+ t.error(err)
1778
+ t.equal(response.statusCode, 200)
1779
+ t.equal(body.toString(), 'hello world')
1780
+ fastify.close()
1781
+ })
1782
+ })
1783
+ })
1784
+
1785
+ test('should be able to add a custom content type parser after removeAllContentTypeParsers', t => {
1786
+ t.plan(5)
1787
+
1788
+ const fastify = Fastify()
1789
+
1790
+ fastify.post('/', (req, reply) => {
1791
+ reply.send(req.body)
1792
+ })
1793
+
1794
+ fastify.removeAllContentTypeParsers()
1795
+
1796
+ fastify.addContentTypeParser('application/jsoff', function (req, payload, done) {
1797
+ t.ok('called')
1798
+ jsonParser(payload, function (err, body) {
1799
+ done(err, body)
1800
+ })
1801
+ })
1802
+
1803
+ fastify.listen(0, err => {
1804
+ t.error(err)
1805
+
1806
+ sget({
1807
+ method: 'POST',
1808
+ url: 'http://localhost:' + fastify.server.address().port,
1809
+ body: '{"hello":"world"}',
1810
+ headers: {
1811
+ 'Content-Type': 'application/jsoff'
1812
+ }
1813
+ }, (err, response, body) => {
1814
+ t.error(err)
1815
+ t.equal(response.statusCode, 200)
1816
+ t.same(body.toString(), JSON.stringify({ hello: 'world' }))
1817
+ fastify.close()
1818
+ })
1819
+ })
1820
+ })
@@ -17,6 +17,15 @@ test('server methods should exist', t => {
17
17
  t.ok(fastify.hasDecorator)
18
18
  })
19
19
 
20
+ test('should check if the given decoration already exist when null', t => {
21
+ t.plan(1)
22
+ const fastify = Fastify()
23
+ fastify.decorate('null', null)
24
+ fastify.ready(() => {
25
+ t.ok(fastify.hasDecorator('null'))
26
+ })
27
+ })
28
+
20
29
  test('server methods should be encapsulated via .register', t => {
21
30
  t.plan(2)
22
31
  const fastify = Fastify()
@@ -425,6 +434,15 @@ test('hasRequestDecorator', t => {
425
434
  t.ok(fastify.hasRequestDecorator(requestDecoratorName))
426
435
  })
427
436
 
437
+ t.test('should check if the given request decoration already exist when null', t => {
438
+ t.plan(2)
439
+ const fastify = Fastify()
440
+
441
+ t.notOk(fastify.hasRequestDecorator(requestDecoratorName))
442
+ fastify.decorateRequest(requestDecoratorName, null)
443
+ t.ok(fastify.hasRequestDecorator(requestDecoratorName))
444
+ })
445
+
428
446
  t.test('should be plugin encapsulable', t => {
429
447
  t.plan(4)
430
448
  const fastify = Fastify()
@@ -481,6 +499,15 @@ test('hasReplyDecorator', t => {
481
499
  t.ok(fastify.hasReplyDecorator(replyDecoratorName))
482
500
  })
483
501
 
502
+ t.test('should check if the given reply decoration already exist when null', t => {
503
+ t.plan(2)
504
+ const fastify = Fastify()
505
+
506
+ t.notOk(fastify.hasReplyDecorator(replyDecoratorName))
507
+ fastify.decorateReply(replyDecoratorName, null)
508
+ t.ok(fastify.hasReplyDecorator(replyDecoratorName))
509
+ })
510
+
484
511
  t.test('should be plugin encapsulable', t => {
485
512
  t.plan(4)
486
513
  const fastify = Fastify()