fastify 3.27.2 → 4.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +5 -4
  2. package/build/build-error-serializer.js +27 -0
  3. package/build/build-validation.js +47 -35
  4. package/docs/Migration-Guide-V4.md +12 -0
  5. package/docs/Reference/ContentTypeParser.md +4 -0
  6. package/docs/Reference/Errors.md +51 -6
  7. package/docs/Reference/Hooks.md +4 -7
  8. package/docs/Reference/LTS.md +5 -4
  9. package/docs/Reference/Reply.md +23 -22
  10. package/docs/Reference/Request.md +1 -3
  11. package/docs/Reference/Routes.md +17 -10
  12. package/docs/Reference/Server.md +48 -63
  13. package/docs/Reference/TypeScript.md +11 -13
  14. package/docs/Reference/Validation-and-Serialization.md +28 -53
  15. package/docs/Type-Providers.md +257 -0
  16. package/examples/hooks.js +1 -1
  17. package/examples/simple-stream.js +18 -0
  18. package/fastify.d.ts +34 -22
  19. package/fastify.js +37 -35
  20. package/lib/configValidator.js +902 -1023
  21. package/lib/contentTypeParser.js +6 -16
  22. package/lib/context.js +36 -10
  23. package/lib/decorate.js +3 -1
  24. package/lib/error-handler.js +158 -0
  25. package/lib/error-serializer.js +257 -0
  26. package/lib/errors.js +43 -9
  27. package/lib/fourOhFour.js +31 -20
  28. package/lib/handleRequest.js +10 -13
  29. package/lib/hooks.js +14 -9
  30. package/lib/pluginOverride.js +0 -3
  31. package/lib/pluginUtils.js +3 -2
  32. package/lib/reply.js +28 -157
  33. package/lib/request.js +13 -10
  34. package/lib/route.js +131 -138
  35. package/lib/schema-controller.js +2 -2
  36. package/lib/schemas.js +27 -1
  37. package/lib/server.js +219 -116
  38. package/lib/symbols.js +4 -3
  39. package/lib/validation.js +2 -1
  40. package/lib/warnings.js +2 -12
  41. package/lib/wrapThenable.js +4 -11
  42. package/package.json +31 -35
  43. package/test/404s.test.js +243 -110
  44. package/test/500s.test.js +2 -2
  45. package/test/async-await.test.js +13 -69
  46. package/test/content-parser.test.js +32 -0
  47. package/test/context-config.test.js +52 -0
  48. package/test/custom-http-server.test.js +14 -7
  49. package/test/custom-parser-async.test.js +0 -65
  50. package/test/custom-parser.test.js +54 -121
  51. package/test/decorator.test.js +1 -3
  52. package/test/delete.test.js +5 -5
  53. package/test/encapsulated-error-handler.test.js +50 -0
  54. package/test/esm/index.test.js +0 -14
  55. package/test/fastify-instance.test.js +4 -4
  56. package/test/fluent-schema.test.js +4 -4
  57. package/test/get.test.js +3 -3
  58. package/test/helper.js +18 -3
  59. package/test/hooks-async.test.js +14 -47
  60. package/test/hooks.on-ready.test.js +9 -4
  61. package/test/hooks.test.js +58 -99
  62. package/test/http2/closing.test.js +5 -11
  63. package/test/http2/unknown-http-method.test.js +3 -9
  64. package/test/https/custom-https-server.test.js +12 -6
  65. package/test/input-validation.js +2 -2
  66. package/test/internals/handleRequest.test.js +3 -40
  67. package/test/internals/initialConfig.test.js +33 -12
  68. package/test/internals/reply.test.js +245 -3
  69. package/test/internals/request.test.js +13 -7
  70. package/test/internals/server.test.js +88 -0
  71. package/test/listen.test.js +84 -1
  72. package/test/logger.test.js +80 -40
  73. package/test/maxRequestsPerSocket.test.js +6 -4
  74. package/test/middleware.test.js +2 -25
  75. package/test/nullable-validation.test.js +51 -14
  76. package/test/plugin.test.js +31 -5
  77. package/test/pretty-print.test.js +22 -10
  78. package/test/reply-error.test.js +123 -12
  79. package/test/request-error.test.js +2 -5
  80. package/test/route-hooks.test.js +17 -17
  81. package/test/route-prefix.test.js +2 -1
  82. package/test/route.test.js +204 -20
  83. package/test/router-options.test.js +1 -1
  84. package/test/schema-examples.test.js +11 -5
  85. package/test/schema-feature.test.js +24 -19
  86. package/test/schema-serialization.test.js +9 -9
  87. package/test/schema-special-usage.test.js +14 -81
  88. package/test/schema-validation.test.js +9 -9
  89. package/test/skip-reply-send.test.js +1 -1
  90. package/test/stream.test.js +23 -12
  91. package/test/throw.test.js +8 -5
  92. package/test/type-provider.test.js +20 -0
  93. package/test/types/fastify.test-d.ts +10 -18
  94. package/test/types/import.js +2 -0
  95. package/test/types/import.ts +1 -0
  96. package/test/types/instance.test-d.ts +35 -14
  97. package/test/types/logger.test-d.ts +44 -15
  98. package/test/types/route.test-d.ts +8 -2
  99. package/test/types/schema.test-d.ts +2 -39
  100. package/test/types/type-provider.test-d.ts +417 -0
  101. package/test/validation-error-handling.test.js +8 -8
  102. package/test/versioned-routes.test.js +28 -16
  103. package/test/wrapThenable.test.js +7 -6
  104. package/types/content-type-parser.d.ts +17 -8
  105. package/types/hooks.d.ts +102 -59
  106. package/types/instance.d.ts +124 -104
  107. package/types/logger.d.ts +18 -104
  108. package/types/plugin.d.ts +10 -4
  109. package/types/reply.d.ts +16 -11
  110. package/types/request.d.ts +10 -5
  111. package/types/route.d.ts +42 -31
  112. package/types/schema.d.ts +1 -1
  113. package/types/type-provider.d.ts +99 -0
  114. package/types/utils.d.ts +1 -1
  115. package/lib/schema-compilers.js +0 -12
  116. package/test/emit-warning.test.js +0 -166
@@ -5,11 +5,20 @@ const split = require('split2')
5
5
  const t = require('tap')
6
6
  const test = t.test
7
7
  const sget = require('simple-get').concat
8
- const joi = require('@hapi/joi')
8
+ const joi = require('joi')
9
9
  const Fastify = require('..')
10
10
  const proxyquire = require('proxyquire')
11
11
  const { FST_ERR_INVALID_URL } = require('../lib/errors')
12
12
 
13
+ function getUrl (app) {
14
+ const { address, port } = app.server.address()
15
+ if (address === '::1') {
16
+ return `http://[${address}]:${port}`
17
+ } else {
18
+ return `http://${address}:${port}`
19
+ }
20
+ }
21
+
13
22
  test('route', t => {
14
23
  t.plan(9)
15
24
  const test = t.test
@@ -203,7 +212,7 @@ test('same route definition object on multiple prefixes', async t => {
203
212
  url: '/simple'
204
213
  }
205
214
 
206
- const fastify = Fastify()
215
+ const fastify = Fastify({ exposeHeadRoutes: false })
207
216
 
208
217
  fastify.register(async function (f) {
209
218
  f.addHook('onRoute', (routeOptions) => {
@@ -536,15 +545,15 @@ test('throws when route with empty url', async t => {
536
545
 
537
546
  const fastify = Fastify()
538
547
  try {
539
- await fastify.route({
548
+ fastify.route({
540
549
  method: 'GET',
541
550
  url: '',
542
551
  handler: (req, res) => {
543
552
  res.send('hi!')
544
553
  }
545
- }).ready()
554
+ })
546
555
  } catch (err) {
547
- t.equal(err.message, 'The first character of a path should be `/` or `*`')
556
+ t.equal(err.message, 'The path could not be empty')
548
557
  }
549
558
  })
550
559
 
@@ -553,10 +562,10 @@ test('throws when route with empty url in shorthand declaration', async t => {
553
562
 
554
563
  const fastify = Fastify()
555
564
  try {
556
- await fastify.get(
565
+ fastify.get(
557
566
  '',
558
567
  async function handler () { return {} }
559
- ).ready()
568
+ )
560
569
  } catch (err) {
561
570
  t.equal(err.message, 'The path could not be empty')
562
571
  }
@@ -581,6 +590,86 @@ test('throws when route-level error handler is not a function', t => {
581
590
  }
582
591
  })
583
592
 
593
+ test('Creates a HEAD route for each GET one (default)', t => {
594
+ t.plan(8)
595
+
596
+ const fastify = Fastify()
597
+
598
+ fastify.route({
599
+ method: 'GET',
600
+ path: '/more-coffee',
601
+ handler: (req, reply) => {
602
+ reply.send({ here: 'is coffee' })
603
+ }
604
+ })
605
+
606
+ fastify.route({
607
+ method: 'GET',
608
+ path: '/some-light',
609
+ handler: (req, reply) => {
610
+ reply.send('Get some light!')
611
+ }
612
+ })
613
+
614
+ fastify.inject({
615
+ method: 'HEAD',
616
+ url: '/more-coffee'
617
+ }, (error, res) => {
618
+ t.error(error)
619
+ t.equal(res.statusCode, 200)
620
+ t.equal(res.headers['content-type'], 'application/json; charset=utf-8')
621
+ t.same(res.body, '')
622
+ })
623
+
624
+ fastify.inject({
625
+ method: 'HEAD',
626
+ url: '/some-light'
627
+ }, (error, res) => {
628
+ t.error(error)
629
+ t.equal(res.statusCode, 200)
630
+ t.equal(res.headers['content-type'], 'text/plain; charset=utf-8')
631
+ t.equal(res.body, '')
632
+ })
633
+ })
634
+
635
+ test('Do not create a HEAD route for each GET one (exposeHeadRoutes: false)', t => {
636
+ t.plan(4)
637
+
638
+ const fastify = Fastify({ exposeHeadRoutes: false })
639
+
640
+ fastify.route({
641
+ method: 'GET',
642
+ path: '/more-coffee',
643
+ handler: (req, reply) => {
644
+ reply.send({ here: 'is coffee' })
645
+ }
646
+ })
647
+
648
+ fastify.route({
649
+ method: 'GET',
650
+ path: '/some-light',
651
+ handler: (req, reply) => {
652
+ reply.send('Get some light!')
653
+ }
654
+ })
655
+
656
+ fastify.inject({
657
+ method: 'HEAD',
658
+ url: '/more-coffee'
659
+ }, (error, res) => {
660
+ t.error(error)
661
+ t.equal(res.statusCode, 404)
662
+ })
663
+
664
+ fastify.inject({
665
+ method: 'HEAD',
666
+ url: '/some-light'
667
+ }, (error, res) => {
668
+ t.error(error)
669
+ t.equal(res.statusCode, 404)
670
+ })
671
+ })
672
+
584
673
  test('Creates a HEAD route for each GET one', t => {
585
674
  t.plan(8)
586
675
 
@@ -811,7 +900,7 @@ test('HEAD route should handle properly each response type', t => {
811
900
  }, (error, res) => {
812
901
  t.error(error)
813
902
  t.equal(res.statusCode, 200)
814
- t.equal(res.headers['content-type'], 'application/octet-stream')
903
+ t.equal(res.headers['content-type'], undefined)
815
904
  t.equal(res.headers['content-length'], undefined)
816
905
  t.equal(res.body, '')
817
906
  })
@@ -959,11 +1048,11 @@ test("HEAD route should handle stream.on('error')", t => {
959
1048
  }, (error, res) => {
960
1049
  t.error(error)
961
1050
  t.equal(res.statusCode, 200)
962
- t.equal(res.headers['content-type'], 'application/octet-stream')
1051
+ t.equal(res.headers['content-type'], undefined)
963
1052
  })
964
1053
  })
965
1054
 
966
- test('HEAD route should not be exposed by default', t => {
1055
+ test('HEAD route should be exposed by default', t => {
967
1056
  t.plan(7)
968
1057
 
969
1058
  const resStream = stream.Readable.from('Hello with error!')
@@ -992,7 +1081,7 @@ test('HEAD route should not be exposed by default', t => {
992
1081
  url: '/without-flag'
993
1082
  }, (error, res) => {
994
1083
  t.error(error)
995
- t.equal(res.statusCode, 404)
1084
+ t.equal(res.statusCode, 200)
996
1085
  })
997
1086
 
998
1087
  fastify.inject({
@@ -1248,26 +1337,121 @@ test('Will not try to re-createprefixed HEAD route if it already exists and expo
1248
1337
  t.ok(true)
1249
1338
  })
1250
1339
 
1251
- test('Correct error message is produced if "path" option is used', t => {
1252
- t.plan(2)
1340
+ test('GET route with body schema should throw', t => {
1341
+ t.plan(1)
1253
1342
 
1254
1343
  const fastify = Fastify()
1255
1344
 
1256
1345
  t.throws(() => {
1257
1346
  fastify.route({
1258
1347
  method: 'GET',
1259
- path: '/test'
1348
+ path: '/get',
1349
+ schema: {
1350
+ body: {}
1351
+ },
1352
+ handler: function (req, reply) {
1353
+ reply.send({ hello: 'world' })
1354
+ }
1260
1355
  })
1261
- }, new Error('Missing handler function for GET:/test route.'))
1356
+ }, new Error('Body validation schema for GET:/get route is not supported!'))
1357
+ })
1358
+
1359
+ test('HEAD route with body schema should throw', t => {
1360
+ t.plan(1)
1361
+
1362
+ const fastify = Fastify()
1262
1363
 
1263
1364
  t.throws(() => {
1264
1365
  fastify.route({
1265
- method: 'POST',
1266
- url: '/test',
1267
- handler: function () {},
1268
- errorHandler: ''
1366
+ method: 'HEAD',
1367
+ path: '/shouldThrow',
1368
+ schema: {
1369
+ body: {}
1370
+ },
1371
+ handler: function (req, reply) {
1372
+ reply.send({ hello: 'world' })
1373
+ }
1374
+ })
1375
+ }, new Error('Body validation schema for HEAD:/shouldThrow route is not supported!'))
1376
+ })
1377
+
1378
+ test('[HEAD, GET] route with body schema should throw', t => {
1379
+ t.plan(1)
1380
+
1381
+ const fastify = Fastify()
1382
+
1383
+ t.throws(() => {
1384
+ fastify.route({
1385
+ method: ['HEAD', 'GET'],
1386
+ path: '/shouldThrowHead',
1387
+ schema: {
1388
+ body: {}
1389
+ },
1390
+ handler: function (req, reply) {
1391
+ reply.send({ hello: 'world' })
1392
+ }
1393
+ })
1394
+ }, new Error('Body validation schema for HEAD:/shouldThrowHead route is not supported!'))
1395
+ })
1396
+
1397
+ test('GET route with body schema should throw - shorthand', t => {
1398
+ t.plan(1)
1399
+
1400
+ const fastify = Fastify()
1401
+
1402
+ t.throws(() => {
1403
+ fastify.get('/shouldThrow', {
1404
+ schema: {
1405
+ body: {}
1406
+ }
1407
+ },
1408
+ function (req, reply) {
1409
+ reply.send({ hello: 'world' })
1410
+ }
1411
+ )
1412
+ }, new Error('Body validation schema for GET:/shouldThrow route is not supported!'))
1413
+ })
1414
+
1415
+ test('HEAD route with body schema should throw - shorthand', t => {
1416
+ t.plan(1)
1417
+
1418
+ const fastify = Fastify()
1419
+
1420
+ t.throws(() => {
1421
+ fastify.head('/shouldThrow2', {
1422
+ schema: {
1423
+ body: {}
1424
+ }
1425
+ },
1426
+ function (req, reply) {
1427
+ reply.send({ hello: 'world' })
1428
+ }
1429
+ )
1430
+ }, new Error('Body validation schema for HEAD:/shouldThrow2 route is not supported!'))
1431
+ })
1432
+
1433
+ test('route with non-english characters', t => {
1434
+ t.plan(4)
1435
+
1436
+ const fastify = Fastify()
1437
+
1438
+ fastify.get('/föö', (request, reply) => {
1439
+ reply.send('here /föö')
1440
+ })
1441
+
1442
+ fastify.listen(0, err => {
1443
+ t.error(err)
1444
+ fastify.server.unref()
1445
+
1446
+ sget({
1447
+ method: 'GET',
1448
+ url: getUrl(fastify) + encodeURI('/föö')
1449
+ }, (err, response, body) => {
1450
+ t.error(err)
1451
+ t.equal(response.statusCode, 200)
1452
+ t.equal(body.toString(), 'here /föö')
1269
1453
  })
1270
- }, new Error('Error Handler for POST:/test route, if defined, must be a function'))
1454
+ })
1271
1455
  })
1272
1456
 
1273
1457
  test('invalid url attribute - non string URL', t => {
@@ -133,7 +133,7 @@ test('Should honor frameworkErrors option', t => {
133
133
  },
134
134
  (err, res) => {
135
135
  t.error(err)
136
- t.equal(res.body, '\'%world\' is not a valid url component - FST_ERR_BAD_URL')
136
+ t.equal(res.body, '\'/test/%world\' is not a valid url component - FST_ERR_BAD_URL')
137
137
  }
138
138
  )
139
139
  })
@@ -98,7 +98,13 @@ test('Example - get schema encapsulated', async t => {
98
98
 
99
99
  test('Example - validation', t => {
100
100
  t.plan(1)
101
- const fastify = Fastify()
101
+ const fastify = Fastify({
102
+ ajv: {
103
+ customOptions: {
104
+ allowUnionTypes: true
105
+ }
106
+ }
107
+ })
102
108
  const handler = () => { }
103
109
 
104
110
  const bodyJsonSchema = {
@@ -224,7 +230,7 @@ test('Example Joi', t => {
224
230
  const fastify = Fastify()
225
231
  const handler = () => { }
226
232
 
227
- const Joi = require('@hapi/joi')
233
+ const Joi = require('joi')
228
234
  fastify.post('/the/url', {
229
235
  schema: {
230
236
  body: Joi.object().keys({
@@ -431,7 +437,7 @@ test('Example - schemas examples', t => {
431
437
  }
432
438
  }
433
439
 
434
- fastify.get('/', {
440
+ fastify.post('/', {
435
441
  handler,
436
442
  schema: {
437
443
  body: refToId,
@@ -450,7 +456,7 @@ test('should return custom error messages with ajv-errors', t => {
450
456
 
451
457
  const fastify = Fastify({
452
458
  ajv: {
453
- customOptions: { allErrors: true, jsonPointers: true },
459
+ customOptions: { allErrors: true },
454
460
  plugins: [
455
461
  require('ajv-errors')
456
462
  ]
@@ -545,7 +551,7 @@ test('should return localized error messages with ajv-i18n', t => {
545
551
  }, (err, res) => {
546
552
  t.error(err)
547
553
  t.same(JSON.parse(res.payload), [{
548
- dataPath: '',
554
+ instancePath: '',
549
555
  keyword: 'required',
550
556
  message: 'должно иметь обязательное поле work',
551
557
  params: { missingProperty: 'work' },
@@ -3,6 +3,7 @@
3
3
  const { test } = require('tap')
4
4
  const Fastify = require('..')
5
5
  const fp = require('fastify-plugin')
6
+ const deepClone = require('rfdc')({ circles: true, proto: false })
6
7
  const Ajv = require('ajv')
7
8
  const { kSchemaController } = require('../lib/symbols.js')
8
9
 
@@ -287,7 +288,7 @@ test('First level $ref', t => {
287
288
 
288
289
  test('Customize validator compiler in instance and route', t => {
289
290
  t.plan(28)
290
- const fastify = Fastify()
291
+ const fastify = Fastify({ exposeHeadRoutes: false })
291
292
 
292
293
  fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
293
294
  t.equal(method, 'POST') // run 4 times
@@ -827,7 +828,7 @@ test('Validation context in validation result', t => {
827
828
  t.equal(err.validationContext, 'body')
828
829
  reply.send()
829
830
  })
830
- fastify.get('/', {
831
+ fastify.post('/', {
831
832
  handler: echoParams,
832
833
  schema: {
833
834
  body: {
@@ -840,7 +841,7 @@ test('Validation context in validation result', t => {
840
841
  }
841
842
  })
842
843
  fastify.inject({
843
- method: 'GET',
844
+ method: 'POST',
844
845
  url: '/',
845
846
  payload: {} // body lacks required field, will fail validation
846
847
  }, (err, res) => {
@@ -881,7 +882,7 @@ test('The schema build should not modify the input', t => {
881
882
  ]
882
883
  })
883
884
 
884
- fastify.get('/', {
885
+ fastify.post('/', {
885
886
  schema: {
886
887
  description: 'get',
887
888
  body: { $ref: 'second#' },
@@ -918,7 +919,11 @@ test('Cross schema reference with encapsulation references', t => {
918
919
  t.plan(1)
919
920
 
920
921
  const fastify = Fastify()
921
- fastify.addSchema({ $id: 'http://foo/item', type: 'object', properties: { foo: { type: 'string' } } })
922
+ fastify.addSchema({
923
+ $id: 'http://foo/item',
924
+ type: 'object',
925
+ properties: { foo: { type: 'string' } }
926
+ })
922
927
 
923
928
  const refItem = { $ref: 'http://foo/item#' }
924
929
 
@@ -949,15 +954,15 @@ test('Cross schema reference with encapsulation references', t => {
949
954
  }
950
955
  }
951
956
 
952
- instance.get('/get', { schema: { response: { 200: multipleRef } } }, () => { })
953
- instance.get('/double-get', { schema: { body: multipleRef, response: { 200: multipleRef } } }, () => { })
957
+ instance.get('/get', { schema: { response: { 200: deepClone(multipleRef) } } }, () => { })
958
+ instance.get('/double-get', { schema: { querystring: multipleRef, response: { 200: multipleRef } } }, () => { })
954
959
  instance.post('/post', { schema: { body: multipleRef, response: { 200: multipleRef } } }, () => { })
955
960
  instance.post('/double', { schema: { response: { 200: { $ref: 'encapsulation' } } } }, () => { })
956
961
  done()
957
962
  }, { prefix: '/foo' })
958
963
 
959
964
  fastify.post('/post', { schema: { body: refItem, response: { 200: refItem } } }, () => { })
960
- fastify.get('/get', { schema: { body: refItem, response: { 200: refItem } } }, () => { })
965
+ fastify.get('/get', { schema: { params: refItem, response: { 200: refItem } } }, () => { })
961
966
 
962
967
  fastify.ready(err => {
963
968
  t.error(err)
@@ -1009,7 +1014,7 @@ test('onReady hook has the compilers ready', t => {
1009
1014
  fastify.get(`/${Math.random()}`, {
1010
1015
  handler: (req, reply) => reply.send(),
1011
1016
  schema: {
1012
- body: { type: 'object' },
1017
+ headers: { type: 'object' },
1013
1018
  response: { 200: { type: 'object' } }
1014
1019
  }
1015
1020
  })
@@ -1099,7 +1104,7 @@ test('Check how many AJV instances are built #2 - verify validatorPool', t => {
1099
1104
  })
1100
1105
 
1101
1106
  function addRandomRoute (server) {
1102
- server.get(`/${Math.random()}`,
1107
+ server.post(`/${Math.random()}`,
1103
1108
  { schema: { body: { type: 'object' } } },
1104
1109
  (req, reply) => reply.send()
1105
1110
  )
@@ -1370,7 +1375,7 @@ test('setSchemaController: Inherits correctly parent schemas with a customized v
1370
1375
  })
1371
1376
  const json = res.json()
1372
1377
 
1373
- t.equal(json.message, 'querystring.msg should be array')
1378
+ t.equal(json.message, 'querystring/msg must be array')
1374
1379
  t.equal(json.statusCode, 400)
1375
1380
  t.equal(res.statusCode, 400, 'Should not coearce the string into array')
1376
1381
  })
@@ -1481,7 +1486,7 @@ test('setSchemaController: Inherits buildSerializer from parent if not present w
1481
1486
  const json = res.json()
1482
1487
 
1483
1488
  t.equal(json.statusCode, 400)
1484
- t.equal(json.message, 'querystring.msg should be array')
1489
+ t.equal(json.message, 'querystring/msg must be array')
1485
1490
  t.equal(rootSerializerCalled, 1, 'Should be called from the child')
1486
1491
  t.equal(rootValidatorCalled, 0, 'Should not be called from the child')
1487
1492
  t.equal(childValidatorCalled, 1, 'Should be called from the child')
@@ -1598,7 +1603,7 @@ test('setSchemaController: Inherits buildValidator from parent if not present wi
1598
1603
  const json = res.json()
1599
1604
 
1600
1605
  t.equal(json.statusCode, 400)
1601
- t.equal(json.message, 'querystring.msg should be array')
1606
+ t.equal(json.message, 'querystring/msg must be array')
1602
1607
  t.equal(rootSerializerCalled, 0, 'Should be called from the child')
1603
1608
  t.equal(rootValidatorCalled, 1, 'Should not be called from the child')
1604
1609
  t.equal(childSerializerCalled, 1, 'Should be called from the child')
@@ -1683,14 +1688,14 @@ test('Should throw if not default validator passed', async t => {
1683
1688
  }
1684
1689
  })
1685
1690
 
1686
- t.equal(res.json().message, 'querystring.msg should be array')
1691
+ t.equal(res.json().message, 'querystring/msg must be array')
1687
1692
  t.equal(res.statusCode, 400, 'Should not coearce the string into array')
1688
1693
  } catch (err) {
1689
1694
  t.error(err)
1690
1695
  }
1691
1696
  })
1692
1697
 
1693
- test('Should throw if not default validator passed', async t => {
1698
+ test('Should coerce the array if the default validator is used', async t => {
1694
1699
  t.plan(2)
1695
1700
  const someSchema = {
1696
1701
  $id: 'some',
@@ -1728,7 +1733,7 @@ test('Should throw if not default validator passed', async t => {
1728
1733
  }
1729
1734
  },
1730
1735
  (req, reply) => {
1731
- reply.send({ noop: 'noop' })
1736
+ reply.send(req.query)
1732
1737
  }
1733
1738
  )
1734
1739
 
@@ -1740,12 +1745,12 @@ test('Should throw if not default validator passed', async t => {
1740
1745
  method: 'POST',
1741
1746
  url: '/',
1742
1747
  query: {
1743
- msg: ['string']
1748
+ msg: 'string'
1744
1749
  }
1745
1750
  })
1746
1751
 
1747
- t.equal(res.json().message, 'querystring.msg should be array')
1748
- t.equal(res.statusCode, 400, 'Should not coearce the string into array')
1752
+ t.equal(res.statusCode, 200)
1753
+ t.same(res.json(), { msg: ['string'] }, 'Should coearce the string into array')
1749
1754
  } catch (err) {
1750
1755
  t.error(err)
1751
1756
  }
@@ -163,7 +163,7 @@ test('Use shared schema and $ref with $id in response ($ref to $id)', t => {
163
163
  t.equal(res.statusCode, 400)
164
164
  t.same(res.json(), {
165
165
  error: 'Bad Request',
166
- message: "body should have required property 'address'",
166
+ message: "body must have required property 'address'",
167
167
  statusCode: 400
168
168
  })
169
169
  })
@@ -236,8 +236,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s
236
236
  $schema: 'http://json-schema.org/draft-07/schema#',
237
237
  title: 'List of Asset locations',
238
238
  type: 'array',
239
- items: { $ref: 'http://example.com/asset.json#' },
240
- default: []
239
+ items: { $ref: 'http://example.com/asset.json#' }
241
240
  }
242
241
 
243
242
  fastify.post('/', {
@@ -273,7 +272,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s
273
272
  t.equal(res.statusCode, 400)
274
273
  t.same(res.json(), {
275
274
  error: 'Bad Request',
276
- message: 'body[0].location.email should match format "email"',
275
+ message: 'body/0/location/email must match format "email"',
277
276
  statusCode: 400
278
277
  })
279
278
  })
@@ -282,7 +281,7 @@ test('Shared schema should be pass to serializer and validator ($ref to shared s
282
281
 
283
282
  test('Custom setSerializerCompiler', t => {
284
283
  t.plan(7)
285
- const fastify = Fastify()
284
+ const fastify = Fastify({ exposeHeadRoutes: false })
286
285
 
287
286
  const outSchema = {
288
287
  $id: 'test',
@@ -355,7 +354,6 @@ test('Custom setSerializerCompiler returns bad serialized output', t => {
355
354
  t.error(err)
356
355
  t.equal(res.statusCode, 500)
357
356
  t.strictSame(res.json(), {
358
- error: 'Internal Server Error',
359
357
  code: 'FST_ERR_REP_INVALID_PAYLOAD_TYPE',
360
358
  message: 'Attempted to send payload of invalid type \'object\'. Expected a string or Buffer.',
361
359
  statusCode: 500
@@ -410,11 +408,11 @@ test('Custom serializer per route', async t => {
410
408
  res = await fastify.inject('/route')
411
409
  t.equal(res.json().mean, 'route')
412
410
 
413
- t.equal(hit, 2, 'the custom and route serializer has been called')
411
+ t.equal(hit, 4, 'the custom and route serializer has been called')
414
412
  })
415
413
 
416
414
  test('Reply serializer win over serializer ', t => {
417
- t.plan(5)
415
+ t.plan(6)
418
416
 
419
417
  const fastify = Fastify()
420
418
  fastify.setReplySerializer(function (payload, statusCode) {
@@ -453,7 +451,7 @@ test('Reply serializer win over serializer ', t => {
453
451
  })
454
452
 
455
453
  test('Reply serializer win over serializer ', t => {
456
- t.plan(5)
454
+ t.plan(6)
457
455
 
458
456
  const fastify = Fastify()
459
457
  fastify.setReplySerializer(function (payload, statusCode) {
@@ -570,11 +568,13 @@ test('do not crash if status code serializer errors', async t => {
570
568
  const fastify = Fastify()
571
569
 
572
570
  const requiresFoo = {
571
+ type: 'object',
573
572
  properties: { foo: { type: 'string' } },
574
573
  required: ['foo']
575
574
  }
576
575
 
577
576
  const someUserErrorType2 = {
577
+ type: 'object',
578
578
  properties: {
579
579
  code: { type: 'number' }
580
580
  },