fastify 3.26.0 → 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 (129) hide show
  1. package/README.md +5 -4
  2. package/build/build-error-serializer.js +27 -0
  3. package/build/build-validation.js +49 -35
  4. package/docs/Guides/Ecosystem.md +2 -1
  5. package/docs/Guides/Prototype-Poisoning.md +3 -3
  6. package/docs/Migration-Guide-V4.md +12 -0
  7. package/docs/Reference/ContentTypeParser.md +8 -1
  8. package/docs/Reference/Errors.md +51 -6
  9. package/docs/Reference/Hooks.md +4 -7
  10. package/docs/Reference/LTS.md +5 -4
  11. package/docs/Reference/Reply.md +23 -22
  12. package/docs/Reference/Request.md +1 -3
  13. package/docs/Reference/Routes.md +17 -10
  14. package/docs/Reference/Server.md +98 -63
  15. package/docs/Reference/TypeScript.md +11 -13
  16. package/docs/Reference/Validation-and-Serialization.md +32 -54
  17. package/docs/Type-Providers.md +257 -0
  18. package/examples/hooks.js +1 -1
  19. package/examples/simple-stream.js +18 -0
  20. package/fastify.d.ts +36 -22
  21. package/fastify.js +72 -53
  22. package/lib/configValidator.js +902 -1023
  23. package/lib/contentTypeParser.js +6 -16
  24. package/lib/context.js +36 -10
  25. package/lib/decorate.js +5 -3
  26. package/lib/error-handler.js +158 -0
  27. package/lib/error-serializer.js +257 -0
  28. package/lib/errors.js +49 -10
  29. package/lib/fourOhFour.js +31 -20
  30. package/lib/handleRequest.js +10 -13
  31. package/lib/hooks.js +14 -9
  32. package/lib/noop-set.js +10 -0
  33. package/lib/pluginOverride.js +0 -3
  34. package/lib/pluginUtils.js +3 -2
  35. package/lib/reply.js +44 -163
  36. package/lib/request.js +13 -10
  37. package/lib/route.js +158 -139
  38. package/lib/schema-controller.js +3 -3
  39. package/lib/schemas.js +27 -1
  40. package/lib/server.js +219 -116
  41. package/lib/symbols.js +6 -4
  42. package/lib/validation.js +2 -1
  43. package/lib/warnings.js +2 -12
  44. package/lib/wrapThenable.js +4 -11
  45. package/package.json +40 -45
  46. package/test/404s.test.js +265 -108
  47. package/test/500s.test.js +2 -2
  48. package/test/async-await.test.js +15 -71
  49. package/test/close.test.js +39 -1
  50. package/test/content-parser.test.js +32 -0
  51. package/test/context-config.test.js +56 -4
  52. package/test/custom-http-server.test.js +14 -7
  53. package/test/custom-parser-async.test.js +0 -65
  54. package/test/custom-parser.test.js +54 -121
  55. package/test/decorator.test.js +1 -3
  56. package/test/delete.test.js +5 -5
  57. package/test/encapsulated-error-handler.test.js +50 -0
  58. package/test/esm/index.test.js +0 -14
  59. package/test/fastify-instance.test.js +4 -4
  60. package/test/fluent-schema.test.js +4 -4
  61. package/test/get.test.js +3 -3
  62. package/test/helper.js +18 -3
  63. package/test/hooks-async.test.js +14 -47
  64. package/test/hooks.on-ready.test.js +9 -4
  65. package/test/hooks.test.js +58 -99
  66. package/test/http2/closing.test.js +5 -11
  67. package/test/http2/unknown-http-method.test.js +3 -9
  68. package/test/https/custom-https-server.test.js +12 -6
  69. package/test/inject.test.js +1 -1
  70. package/test/input-validation.js +2 -2
  71. package/test/internals/all.test.js +2 -2
  72. package/test/internals/contentTypeParser.test.js +4 -4
  73. package/test/internals/handleRequest.test.js +9 -46
  74. package/test/internals/initialConfig.test.js +33 -12
  75. package/test/internals/logger.test.js +1 -1
  76. package/test/internals/reply.test.js +245 -3
  77. package/test/internals/request.test.js +13 -7
  78. package/test/internals/server.test.js +88 -0
  79. package/test/listen.test.js +84 -1
  80. package/test/logger.test.js +98 -58
  81. package/test/maxRequestsPerSocket.test.js +8 -6
  82. package/test/middleware.test.js +2 -25
  83. package/test/noop-set.test.js +19 -0
  84. package/test/nullable-validation.test.js +51 -14
  85. package/test/plugin.test.js +31 -5
  86. package/test/pretty-print.test.js +22 -10
  87. package/test/reply-error.test.js +123 -12
  88. package/test/request-error.test.js +2 -5
  89. package/test/route-hooks.test.js +17 -17
  90. package/test/route-prefix.test.js +2 -1
  91. package/test/route.test.js +216 -20
  92. package/test/router-options.test.js +1 -1
  93. package/test/schema-examples.test.js +11 -5
  94. package/test/schema-feature.test.js +24 -19
  95. package/test/schema-serialization.test.js +50 -9
  96. package/test/schema-special-usage.test.js +14 -81
  97. package/test/schema-validation.test.js +9 -9
  98. package/test/skip-reply-send.test.js +8 -8
  99. package/test/stream.test.js +23 -12
  100. package/test/throw.test.js +8 -5
  101. package/test/trust-proxy.test.js +1 -1
  102. package/test/type-provider.test.js +20 -0
  103. package/test/types/fastify.test-d.ts +12 -18
  104. package/test/types/hooks.test-d.ts +7 -3
  105. package/test/types/import.js +2 -0
  106. package/test/types/import.ts +1 -0
  107. package/test/types/instance.test-d.ts +61 -15
  108. package/test/types/logger.test-d.ts +44 -15
  109. package/test/types/route.test-d.ts +8 -2
  110. package/test/types/schema.test-d.ts +2 -39
  111. package/test/types/type-provider.test-d.ts +417 -0
  112. package/test/validation-error-handling.test.js +9 -9
  113. package/test/versioned-routes.test.js +29 -17
  114. package/test/wrapThenable.test.js +7 -6
  115. package/types/.eslintrc.json +1 -1
  116. package/types/content-type-parser.d.ts +17 -8
  117. package/types/hooks.d.ts +107 -60
  118. package/types/instance.d.ts +137 -105
  119. package/types/logger.d.ts +18 -104
  120. package/types/plugin.d.ts +10 -4
  121. package/types/register.d.ts +1 -1
  122. package/types/reply.d.ts +16 -11
  123. package/types/request.d.ts +10 -5
  124. package/types/route.d.ts +42 -31
  125. package/types/schema.d.ts +15 -1
  126. package/types/type-provider.d.ts +99 -0
  127. package/types/utils.d.ts +1 -1
  128. package/lib/schema-compilers.js +0 -12
  129. package/test/emit-warning.test.js +0 -166
@@ -5,9 +5,19 @@ 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
+ const { FST_ERR_INVALID_URL } = require('../lib/errors')
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
+ }
11
21
 
12
22
  test('route', t => {
13
23
  t.plan(9)
@@ -202,7 +212,7 @@ test('same route definition object on multiple prefixes', async t => {
202
212
  url: '/simple'
203
213
  }
204
214
 
205
- const fastify = Fastify()
215
+ const fastify = Fastify({ exposeHeadRoutes: false })
206
216
 
207
217
  fastify.register(async function (f) {
208
218
  f.addHook('onRoute', (routeOptions) => {
@@ -535,15 +545,15 @@ test('throws when route with empty url', async t => {
535
545
 
536
546
  const fastify = Fastify()
537
547
  try {
538
- await fastify.route({
548
+ fastify.route({
539
549
  method: 'GET',
540
550
  url: '',
541
551
  handler: (req, res) => {
542
552
  res.send('hi!')
543
553
  }
544
- }).ready()
554
+ })
545
555
  } catch (err) {
546
- t.equal(err.message, 'The first character of a path should be `/` or `*`')
556
+ t.equal(err.message, 'The path could not be empty')
547
557
  }
548
558
  })
549
559
 
@@ -552,10 +562,10 @@ test('throws when route with empty url in shorthand declaration', async t => {
552
562
 
553
563
  const fastify = Fastify()
554
564
  try {
555
- await fastify.get(
565
+ fastify.get(
556
566
  '',
557
567
  async function handler () { return {} }
558
- ).ready()
568
+ )
559
569
  } catch (err) {
560
570
  t.equal(err.message, 'The path could not be empty')
561
571
  }
@@ -580,6 +590,86 @@ test('throws when route-level error handler is not a function', t => {
580
590
  }
581
591
  })
582
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
+
583
673
  test('Creates a HEAD route for each GET one', t => {
584
674
  t.plan(8)
585
675
 
@@ -810,7 +900,7 @@ test('HEAD route should handle properly each response type', t => {
810
900
  }, (error, res) => {
811
901
  t.error(error)
812
902
  t.equal(res.statusCode, 200)
813
- t.equal(res.headers['content-type'], 'application/octet-stream')
903
+ t.equal(res.headers['content-type'], undefined)
814
904
  t.equal(res.headers['content-length'], undefined)
815
905
  t.equal(res.body, '')
816
906
  })
@@ -958,11 +1048,11 @@ test("HEAD route should handle stream.on('error')", t => {
958
1048
  }, (error, res) => {
959
1049
  t.error(error)
960
1050
  t.equal(res.statusCode, 200)
961
- t.equal(res.headers['content-type'], 'application/octet-stream')
1051
+ t.equal(res.headers['content-type'], undefined)
962
1052
  })
963
1053
  })
964
1054
 
965
- test('HEAD route should not be exposed by default', t => {
1055
+ test('HEAD route should be exposed by default', t => {
966
1056
  t.plan(7)
967
1057
 
968
1058
  const resStream = stream.Readable.from('Hello with error!')
@@ -991,7 +1081,7 @@ test('HEAD route should not be exposed by default', t => {
991
1081
  url: '/without-flag'
992
1082
  }, (error, res) => {
993
1083
  t.error(error)
994
- t.equal(res.statusCode, 404)
1084
+ t.equal(res.statusCode, 200)
995
1085
  })
996
1086
 
997
1087
  fastify.inject({
@@ -1247,24 +1337,130 @@ test('Will not try to re-createprefixed HEAD route if it already exists and expo
1247
1337
  t.ok(true)
1248
1338
  })
1249
1339
 
1250
- test('Correct error message is produced if "path" option is used', t => {
1251
- t.plan(2)
1340
+ test('GET route with body schema should throw', t => {
1341
+ t.plan(1)
1252
1342
 
1253
1343
  const fastify = Fastify()
1254
1344
 
1255
1345
  t.throws(() => {
1256
1346
  fastify.route({
1257
1347
  method: 'GET',
1258
- path: '/test'
1348
+ path: '/get',
1349
+ schema: {
1350
+ body: {}
1351
+ },
1352
+ handler: function (req, reply) {
1353
+ reply.send({ hello: 'world' })
1354
+ }
1355
+ })
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()
1363
+
1364
+ t.throws(() => {
1365
+ fastify.route({
1366
+ method: 'HEAD',
1367
+ path: '/shouldThrow',
1368
+ schema: {
1369
+ body: {}
1370
+ },
1371
+ handler: function (req, reply) {
1372
+ reply.send({ hello: 'world' })
1373
+ }
1259
1374
  })
1260
- }, new Error('Missing handler function for GET:/test route.'))
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()
1261
1382
 
1262
1383
  t.throws(() => {
1263
1384
  fastify.route({
1264
- method: 'POST',
1265
- url: '/test',
1266
- handler: function () {},
1267
- errorHandler: ''
1385
+ method: ['HEAD', 'GET'],
1386
+ path: '/shouldThrowHead',
1387
+ schema: {
1388
+ body: {}
1389
+ },
1390
+ handler: function (req, reply) {
1391
+ reply.send({ hello: 'world' })
1392
+ }
1268
1393
  })
1269
- }, new Error('Error Handler for POST:/test route, if defined, must be a function'))
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öö')
1453
+ })
1454
+ })
1455
+ })
1456
+
1457
+ test('invalid url attribute - non string URL', t => {
1458
+ t.plan(1)
1459
+ const fastify = Fastify()
1460
+
1461
+ try {
1462
+ fastify.get(/^\/(donations|skills|blogs)/, () => {})
1463
+ } catch (error) {
1464
+ t.equal(error.code, FST_ERR_INVALID_URL().code)
1465
+ }
1270
1466
  })
@@ -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
  },
@@ -670,3 +670,44 @@ test('error in custom schema serialize compiler, throw FST_ERR_SCH_SERIALIZATION
670
670
  t.equal(err.code, 'FST_ERR_SCH_SERIALIZATION_BUILD')
671
671
  })
672
672
  })
673
+
674
+ test('Errors in searilizer sended to errorHandler', async t => {
675
+ let savedError
676
+
677
+ const fastify = Fastify()
678
+ fastify.get('/', {
679
+ schema: {
680
+ response: {
681
+ 200: {
682
+ type: 'object',
683
+ properties: {
684
+ name: { type: 'string' },
685
+ power: { type: 'string' }
686
+ },
687
+ required: ['name']
688
+ }
689
+ }
690
+ }
691
+
692
+ }, function (req, reply) {
693
+ reply.code(200).send({ no: 'thing' })
694
+ })
695
+ fastify.setErrorHandler((error, request, reply) => {
696
+ savedError = error
697
+ reply.code(500).send(error)
698
+ })
699
+
700
+ const res = await fastify.inject('/')
701
+
702
+ t.equal(res.statusCode, 500)
703
+
704
+ // t.same(savedError, new Error('"name" is required!'));
705
+ t.same(res.json(), {
706
+ statusCode: 500,
707
+ error: 'Internal Server Error',
708
+ message: '"name" is required!'
709
+ })
710
+ t.ok(savedError, 'error presents')
711
+ t.ok(savedError.serialization, 'Serialization sign presents')
712
+ t.end()
713
+ })