fastify 4.25.2 → 4.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.vscode/settings.json +22 -0
  2. package/EXPENSE_POLICY.md +105 -0
  3. package/GOVERNANCE.md +2 -103
  4. package/LICENSE +1 -1
  5. package/README.md +13 -9
  6. package/SECURITY.md +2 -157
  7. package/SPONSORS.md +20 -0
  8. package/build/build-validation.js +3 -1
  9. package/docs/Guides/Ecosystem.md +27 -9
  10. package/docs/Guides/Getting-Started.md +16 -3
  11. package/docs/Guides/Style-Guide.md +7 -7
  12. package/docs/Reference/Decorators.md +1 -1
  13. package/docs/Reference/Errors.md +63 -1
  14. package/docs/Reference/Hooks.md +1 -1
  15. package/docs/Reference/Logging.md +3 -3
  16. package/docs/Reference/Reply.md +70 -1
  17. package/docs/Reference/Server.md +90 -0
  18. package/docs/Reference/Warnings.md +2 -0
  19. package/fastify.d.ts +3 -2
  20. package/fastify.js +25 -7
  21. package/lib/configValidator.js +62 -33
  22. package/lib/contentTypeParser.js +9 -2
  23. package/lib/error-handler.js +1 -1
  24. package/lib/error-serializer.js +2 -0
  25. package/lib/errors.js +4 -0
  26. package/lib/fourOhFour.js +4 -3
  27. package/lib/hooks.js +1 -5
  28. package/lib/reply.js +68 -10
  29. package/lib/reqIdGenFactory.js +5 -0
  30. package/lib/route.js +22 -6
  31. package/lib/schema-controller.js +37 -4
  32. package/lib/symbols.js +1 -0
  33. package/lib/warnings.js +6 -0
  34. package/package.json +17 -5
  35. package/test/async_hooks.test.js +69 -0
  36. package/test/findRoute.test.js +135 -0
  37. package/test/genReqId.test.js +392 -0
  38. package/test/hooks.on-listen.test.js +66 -14
  39. package/test/internals/errors.test.js +17 -7
  40. package/test/internals/initialConfig.test.js +7 -3
  41. package/test/internals/reply.test.js +80 -5
  42. package/test/schema-serialization.test.js +41 -0
  43. package/test/schema-validation.test.js +115 -6
  44. package/test/serialize-response.test.js +187 -0
  45. package/test/types/instance.test-d.ts +14 -1
  46. package/test/types/reply.test-d.ts +4 -2
  47. package/test/types/route.test-d.ts +15 -1
  48. package/test/useSemicolonDelimiter.test.js +113 -0
  49. package/test/web-api.test.js +208 -0
  50. package/types/instance.d.ts +23 -10
  51. package/types/reply.d.ts +4 -0
  52. package/test/types/import.js +0 -2
@@ -5,9 +5,7 @@ const Fastify = require('../fastify')
5
5
  const fp = require('fastify-plugin')
6
6
  const split = require('split2')
7
7
  const helper = require('./helper')
8
-
9
- // fix citgm @aix72-ppc64
10
- const LISTEN_READYNESS = process.env.CITGM ? 250 : 50
8
+ const { kState } = require('../lib/symbols')
11
9
 
12
10
  let localhost
13
11
  before(async function () {
@@ -274,8 +272,8 @@ test('localhost Register onListen hook after a plugin inside a plugin should log
274
272
  })
275
273
  })
276
274
 
277
- test('localhost onListen encapsulation should be called in order', t => {
278
- t.plan(6)
275
+ test('localhost onListen encapsulation should be called in order', async t => {
276
+ t.plan(8)
279
277
  const fastify = Fastify()
280
278
  t.teardown(fastify.close.bind(fastify))
281
279
 
@@ -287,20 +285,75 @@ test('localhost onListen encapsulation should be called in order', t => {
287
285
  done()
288
286
  })
289
287
 
290
- fastify.register(async (childOne, o) => {
288
+ await fastify.register(async (childOne, o) => {
291
289
  childOne.addHook('onListen', function (done) {
292
290
  t.equal(++order, 2, 'called in childOne')
293
291
  t.equal(this.pluginName, childOne.pluginName, 'the this binding is the right instance')
294
292
  done()
295
293
  })
296
- childOne.register(async (childTwo, o) => {
294
+
295
+ await childOne.register(async (childTwo, o) => {
297
296
  childTwo.addHook('onListen', async function () {
298
297
  t.equal(++order, 3, 'called in childTwo')
299
298
  t.equal(this.pluginName, childTwo.pluginName, 'the this binding is the right instance')
300
299
  })
301
300
  })
301
+
302
+ await childOne.register(async (childTwoPeer, o) => {
303
+ childTwoPeer.addHook('onListen', async function () {
304
+ t.equal(++order, 4, 'called second in childTwo')
305
+ t.equal(this.pluginName, childTwoPeer.pluginName, 'the this binding is the right instance')
306
+ })
307
+ })
302
308
  })
303
- fastify.listen({
309
+ await fastify.listen({
310
+ host: 'localhost',
311
+ port: 0
312
+ })
313
+ })
314
+
315
+ test('localhost onListen encapsulation with only nested hook', async t => {
316
+ t.plan(1)
317
+ const fastify = Fastify()
318
+ t.teardown(fastify.close.bind(fastify))
319
+
320
+ await fastify.register(async (child) => {
321
+ await child.register(async (child2) => {
322
+ child2.addHook('onListen', function (done) {
323
+ t.pass()
324
+ done()
325
+ })
326
+ })
327
+ })
328
+
329
+ await fastify.listen({
330
+ host: 'localhost',
331
+ port: 0
332
+ })
333
+ })
334
+
335
+ test('localhost onListen peer encapsulations with only nested hooks', async t => {
336
+ t.plan(2)
337
+ const fastify = Fastify()
338
+ t.teardown(fastify.close.bind(fastify))
339
+
340
+ await fastify.register(async (child) => {
341
+ await child.register(async (child2) => {
342
+ child2.addHook('onListen', function (done) {
343
+ t.pass()
344
+ done()
345
+ })
346
+ })
347
+
348
+ await child.register(async (child2) => {
349
+ child2.addHook('onListen', function (done) {
350
+ t.pass()
351
+ done()
352
+ })
353
+ })
354
+ })
355
+
356
+ await fastify.listen({
304
357
  host: 'localhost',
305
358
  port: 0
306
359
  })
@@ -1056,36 +1109,35 @@ test('async onListen does not need to be awaited', t => {
1056
1109
 
1057
1110
  test('onListen hooks do not block /1', t => {
1058
1111
  t.plan(2)
1112
+
1059
1113
  const fastify = Fastify()
1060
1114
  t.teardown(fastify.close.bind(fastify))
1061
1115
 
1062
1116
  fastify.addHook('onListen', function (done) {
1063
- setTimeout(done, 500)
1117
+ t.equal(fastify[kState].listening, true)
1118
+ done()
1064
1119
  })
1065
1120
 
1066
- const startDate = new Date()
1067
1121
  fastify.listen({
1068
1122
  host: 'localhost',
1069
1123
  port: 0
1070
1124
  }, err => {
1071
1125
  t.error(err)
1072
- t.ok(new Date() - startDate < LISTEN_READYNESS)
1073
1126
  })
1074
1127
  })
1075
1128
 
1076
1129
  test('onListen hooks do not block /2', async t => {
1077
1130
  t.plan(1)
1131
+
1078
1132
  const fastify = Fastify()
1079
1133
  t.teardown(fastify.close.bind(fastify))
1080
1134
 
1081
1135
  fastify.addHook('onListen', async function () {
1082
- await new Promise(resolve => setTimeout(resolve, 500))
1136
+ t.equal(fastify[kState].listening, true)
1083
1137
  })
1084
1138
 
1085
- const startDate = new Date()
1086
1139
  await fastify.listen({
1087
1140
  host: 'localhost',
1088
1141
  port: 0
1089
1142
  })
1090
- t.ok(new Date() - startDate < LISTEN_READYNESS)
1091
1143
  })
@@ -5,7 +5,7 @@ const errors = require('../../lib/errors')
5
5
  const { readFileSync } = require('node:fs')
6
6
  const { resolve } = require('node:path')
7
7
 
8
- test('should expose 78 errors', t => {
8
+ test('should expose 79 errors', t => {
9
9
  t.plan(1)
10
10
  const exportedKeys = Object.keys(errors)
11
11
  let counter = 0
@@ -14,11 +14,11 @@ test('should expose 78 errors', t => {
14
14
  counter++
15
15
  }
16
16
  }
17
- t.equal(counter, 78)
17
+ t.equal(counter, 79)
18
18
  })
19
19
 
20
20
  test('ensure name and codes of Errors are identical', t => {
21
- t.plan(78)
21
+ t.plan(79)
22
22
  const exportedKeys = Object.keys(errors)
23
23
  for (const key of exportedKeys) {
24
24
  if (errors[key].name === 'FastifyError') {
@@ -337,6 +337,16 @@ test('FST_ERR_REP_INVALID_PAYLOAD_TYPE', t => {
337
337
  t.ok(error instanceof TypeError)
338
338
  })
339
339
 
340
+ test('FST_ERR_REP_RESPONSE_BODY_CONSUMED', t => {
341
+ t.plan(5)
342
+ const error = new errors.FST_ERR_REP_RESPONSE_BODY_CONSUMED()
343
+ t.equal(error.name, 'FastifyError')
344
+ t.equal(error.code, 'FST_ERR_REP_RESPONSE_BODY_CONSUMED')
345
+ t.equal(error.message, 'Response.body is already consumed.')
346
+ t.equal(error.statusCode, 500)
347
+ t.ok(error instanceof Error)
348
+ })
349
+
340
350
  test('FST_ERR_REP_ALREADY_SENT', t => {
341
351
  t.plan(5)
342
352
  const error = new errors.FST_ERR_REP_ALREADY_SENT('/hello', 'GET')
@@ -818,7 +828,7 @@ test('FST_ERR_LISTEN_OPTIONS_INVALID', t => {
818
828
  })
819
829
 
820
830
  test('Ensure that all errors are in Errors.md TOC', t => {
821
- t.plan(78)
831
+ t.plan(79)
822
832
  const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8')
823
833
 
824
834
  const exportedKeys = Object.keys(errors)
@@ -830,7 +840,7 @@ test('Ensure that all errors are in Errors.md TOC', t => {
830
840
  })
831
841
 
832
842
  test('Ensure that non-existing errors are not in Errors.md TOC', t => {
833
- t.plan(78)
843
+ t.plan(79)
834
844
  const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8')
835
845
 
836
846
  const matchRE = / {4}- \[([A-Z0-9_]+)\]\(#[a-z0-9_]+\)/g
@@ -843,7 +853,7 @@ test('Ensure that non-existing errors are not in Errors.md TOC', t => {
843
853
  })
844
854
 
845
855
  test('Ensure that all errors are in Errors.md documented', t => {
846
- t.plan(78)
856
+ t.plan(79)
847
857
  const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8')
848
858
 
849
859
  const exportedKeys = Object.keys(errors)
@@ -855,7 +865,7 @@ test('Ensure that all errors are in Errors.md documented', t => {
855
865
  })
856
866
 
857
867
  test('Ensure that non-existing errors are not in Errors.md documented', t => {
858
- t.plan(78)
868
+ t.plan(79)
859
869
  const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8')
860
870
 
861
871
  const matchRE = /<a id="[0-9a-zA-Z_]+">([0-9a-zA-Z_]+)<\/a>/g
@@ -48,14 +48,15 @@ test('without options passed to Fastify, initialConfig should expose default val
48
48
  requestIdHeader: 'request-id',
49
49
  requestIdLogLabel: 'reqId',
50
50
  http2SessionTimeout: 72000,
51
- exposeHeadRoutes: true
51
+ exposeHeadRoutes: true,
52
+ useSemicolonDelimiter: true
52
53
  }
53
54
 
54
55
  t.same(Fastify().initialConfig, fastifyDefaultOptions)
55
56
  })
56
57
 
57
58
  test('Fastify.initialConfig should expose all options', t => {
58
- t.plan(21)
59
+ t.plan(22)
59
60
 
60
61
  const serverFactory = (handler, opts) => {
61
62
  const server = http.createServer((req, res) => {
@@ -99,6 +100,7 @@ test('Fastify.initialConfig should expose all options', t => {
99
100
  allowUnsafeRegex: false,
100
101
  requestIdHeader: 'request-id-alt',
101
102
  pluginTimeout: 20000,
103
+ useSemicolonDelimiter: false,
102
104
  querystringParser: str => str,
103
105
  genReqId: function (req) {
104
106
  return reqId++
@@ -123,6 +125,7 @@ test('Fastify.initialConfig should expose all options', t => {
123
125
  t.equal(fastify.initialConfig.bodyLimit, 1049600)
124
126
  t.equal(fastify.initialConfig.onProtoPoisoning, 'remove')
125
127
  t.equal(fastify.initialConfig.caseSensitive, true)
128
+ t.equal(fastify.initialConfig.useSemicolonDelimiter, false)
126
129
  t.equal(fastify.initialConfig.allowUnsafeRegex, false)
127
130
  t.equal(fastify.initialConfig.requestIdHeader, 'request-id-alt')
128
131
  t.equal(fastify.initialConfig.pluginTimeout, 20000)
@@ -285,7 +288,8 @@ test('Should not have issues when passing stream options to Pino.js', t => {
285
288
  requestIdHeader: 'request-id',
286
289
  requestIdLogLabel: 'reqId',
287
290
  http2SessionTimeout: 72000,
288
- exposeHeadRoutes: true
291
+ exposeHeadRoutes: true,
292
+ useSemicolonDelimiter: true
289
293
  })
290
294
  } catch (error) {
291
295
  t.fail()
@@ -19,7 +19,7 @@ const {
19
19
  } = require('../../lib/symbols')
20
20
  const fs = require('node:fs')
21
21
  const path = require('node:path')
22
- const { FSTDEP019, FSTDEP010 } = require('../../lib/warnings')
22
+ const { FSTDEP010, FSTDEP019, FSTDEP020 } = require('../../lib/warnings')
23
23
 
24
24
  const agent = new http.Agent({ keepAlive: false })
25
25
 
@@ -1598,7 +1598,40 @@ test('reply.getResponseTime() should return a number greater than 0 after the ti
1598
1598
  fastify.inject({ method: 'GET', url: '/' })
1599
1599
  })
1600
1600
 
1601
- test('reply.getResponseTime() should return the time since a request started while inflight', t => {
1601
+ test('should emit deprecation warning when trying to use reply.getResponseTime() and should return the time since a request started while inflight', t => {
1602
+ t.plan(5)
1603
+ const fastify = Fastify()
1604
+ fastify.route({
1605
+ method: 'GET',
1606
+ url: '/',
1607
+ handler: (req, reply) => {
1608
+ reply.send('hello world')
1609
+ }
1610
+ })
1611
+
1612
+ process.removeAllListeners('warning')
1613
+ process.on('warning', onWarning)
1614
+ function onWarning (warning) {
1615
+ t.equal(warning.name, 'DeprecationWarning')
1616
+ t.equal(warning.code, FSTDEP020.code)
1617
+ }
1618
+
1619
+ fastify.addHook('preValidation', (req, reply, done) => {
1620
+ t.equal(reply.getResponseTime(), reply.getResponseTime())
1621
+ done()
1622
+ })
1623
+
1624
+ fastify.inject({ method: 'GET', url: '/' }, (err, res) => {
1625
+ t.error(err)
1626
+ t.pass()
1627
+
1628
+ process.removeListener('warning', onWarning)
1629
+ })
1630
+
1631
+ FSTDEP020.emitted = false
1632
+ })
1633
+
1634
+ test('reply.getResponseTime() should return the same value after a request is finished', t => {
1602
1635
  t.plan(1)
1603
1636
  const fastify = Fastify()
1604
1637
  fastify.route({
@@ -1609,19 +1642,61 @@ test('reply.getResponseTime() should return the time since a request started whi
1609
1642
  }
1610
1643
  })
1611
1644
 
1645
+ fastify.addHook('onResponse', (req, reply) => {
1646
+ t.equal(reply.getResponseTime(), reply.getResponseTime())
1647
+ t.end()
1648
+ })
1649
+
1650
+ fastify.inject({ method: 'GET', url: '/' })
1651
+ })
1652
+
1653
+ test('reply.elapsedTime should return a number greater than 0 after the timer is initialised on the reply by setting up response listeners', t => {
1654
+ t.plan(1)
1655
+ const fastify = Fastify()
1656
+ fastify.route({
1657
+ method: 'GET',
1658
+ url: '/',
1659
+ handler: (req, reply) => {
1660
+ reply.send('hello world')
1661
+ }
1662
+ })
1663
+
1664
+ fastify.addHook('onResponse', (req, reply) => {
1665
+ t.ok(reply.elapsedTime > 0)
1666
+ t.end()
1667
+ })
1668
+
1669
+ fastify.inject({ method: 'GET', url: '/' })
1670
+ })
1671
+
1672
+ test('reply.elapsedTime should return the time since a request started while inflight', t => {
1673
+ t.plan(1)
1674
+ const fastify = Fastify()
1675
+ fastify.route({
1676
+ method: 'GET',
1677
+ url: '/',
1678
+ handler: (req, reply) => {
1679
+ reply.send('hello world')
1680
+ }
1681
+ })
1682
+
1683
+ let preValidationElapsedTime
1684
+
1612
1685
  fastify.addHook('preValidation', (req, reply, done) => {
1613
- t.not(reply.getResponseTime(), reply.getResponseTime())
1686
+ preValidationElapsedTime = reply.elapsedTime
1687
+
1614
1688
  done()
1615
1689
  })
1616
1690
 
1617
1691
  fastify.addHook('onResponse', (req, reply) => {
1692
+ t.ok(reply.elapsedTime > preValidationElapsedTime)
1618
1693
  t.end()
1619
1694
  })
1620
1695
 
1621
1696
  fastify.inject({ method: 'GET', url: '/' })
1622
1697
  })
1623
1698
 
1624
- test('reply.getResponseTime() should return the same value after a request is finished', t => {
1699
+ test('reply.elapsedTime should return the same value after a request is finished', t => {
1625
1700
  t.plan(1)
1626
1701
  const fastify = Fastify()
1627
1702
  fastify.route({
@@ -1633,7 +1708,7 @@ test('reply.getResponseTime() should return the same value after a request is fi
1633
1708
  })
1634
1709
 
1635
1710
  fastify.addHook('onResponse', (req, reply) => {
1636
- t.equal(reply.getResponseTime(), reply.getResponseTime())
1711
+ t.equal(reply.elapsedTime, reply.elapsedTime)
1637
1712
  t.end()
1638
1713
  })
1639
1714
 
@@ -593,6 +593,47 @@ test('Custom setSerializerCompiler returns bad serialized output', t => {
593
593
  })
594
594
  })
595
595
 
596
+ test('Custom setSerializerCompiler with addSchema', t => {
597
+ t.plan(6)
598
+ const fastify = Fastify({ exposeHeadRoutes: false })
599
+
600
+ const outSchema = {
601
+ $id: 'test',
602
+ type: 'object',
603
+ whatever: 'need to be parsed by the custom serializer'
604
+ }
605
+
606
+ fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => {
607
+ t.equal(method, 'GET')
608
+ t.equal(url, '/foo/:id')
609
+ t.equal(httpStatus, '200')
610
+ t.same(schema, outSchema)
611
+ return _data => JSON.stringify({ id: 2 })
612
+ })
613
+
614
+ // provoke re-creation of serialization compiler in setupSerializer
615
+ fastify.addSchema({ $id: 'dummy', type: 'object' })
616
+
617
+ fastify.get('/foo/:id', {
618
+ handler (_req, reply) {
619
+ reply.send({ id: 1 })
620
+ },
621
+ schema: {
622
+ response: {
623
+ 200: outSchema
624
+ }
625
+ }
626
+ })
627
+
628
+ fastify.inject({
629
+ method: 'GET',
630
+ url: '/foo/123'
631
+ }, (err, res) => {
632
+ t.error(err)
633
+ t.equal(res.payload, JSON.stringify({ id: 2 }))
634
+ })
635
+ })
636
+
596
637
  test('Custom serializer per route', async t => {
597
638
  const fastify = Fastify()
598
639
 
@@ -106,7 +106,7 @@ test('Basic validation test', t => {
106
106
  })
107
107
 
108
108
  test('External AJV instance', t => {
109
- t.plan(4)
109
+ t.plan(5)
110
110
 
111
111
  const fastify = Fastify()
112
112
  const ajv = new AJV()
@@ -118,6 +118,7 @@ test('External AJV instance', t => {
118
118
  fastify.addSchema(schemaBRefToA)
119
119
 
120
120
  fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
121
+ t.pass('custom validator compiler called')
121
122
  return ajv.compile(schema)
122
123
  })
123
124
 
@@ -151,7 +152,7 @@ test('External AJV instance', t => {
151
152
  })
152
153
 
153
154
  test('Encapsulation', t => {
154
- t.plan(19)
155
+ t.plan(21)
155
156
 
156
157
  const fastify = Fastify()
157
158
  const ajv = new AJV()
@@ -164,6 +165,7 @@ test('Encapsulation', t => {
164
165
 
165
166
  fastify.register((instance, opts, done) => {
166
167
  const validator = ({ schema, method, url, httpPart }) => {
168
+ t.pass('custom validator compiler called')
167
169
  return ajv.compile(schema)
168
170
  }
169
171
  instance.setValidatorCompiler(validator)
@@ -271,7 +273,7 @@ test('Encapsulation', t => {
271
273
  })
272
274
 
273
275
  test('Triple $ref with a simple $id', t => {
274
- t.plan(6)
276
+ t.plan(7)
275
277
 
276
278
  const fastify = Fastify()
277
279
  const ajv = new AJV()
@@ -285,6 +287,7 @@ test('Triple $ref with a simple $id', t => {
285
287
  fastify.addSchema(schemaCRefToB)
286
288
 
287
289
  fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
290
+ t.pass('custom validator compiler called')
288
291
  return ajv.compile(schema)
289
292
  })
290
293
 
@@ -1024,19 +1027,125 @@ test("The same $id in route's schema must not overwrite others", t => {
1024
1027
 
1025
1028
  test('Custom validator compiler should not mutate schema', async t => {
1026
1029
  t.plan(2)
1027
- class Headers {}
1030
+ class Headers { }
1028
1031
  const fastify = Fastify()
1029
1032
 
1030
1033
  fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {
1031
1034
  t.type(schema, Headers)
1032
- return () => {}
1035
+ return () => { }
1033
1036
  })
1034
1037
 
1035
1038
  fastify.get('/', {
1036
1039
  schema: {
1037
1040
  headers: new Headers()
1038
1041
  }
1039
- }, () => {})
1042
+ }, () => { })
1043
+
1044
+ await fastify.ready()
1045
+ })
1046
+
1047
+ test('Custom validator builder override by custom validator compiler', async t => {
1048
+ t.plan(3)
1049
+ const ajvDefaults = {
1050
+ removeAdditional: true,
1051
+ coerceTypes: true,
1052
+ allErrors: true
1053
+ }
1054
+ const ajv1 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_one', type: 'object', validator: () => true })
1055
+ const ajv2 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_two', type: 'object', validator: () => true })
1056
+ const fastify = Fastify({ schemaController: { compilersFactory: { buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) } } })
1057
+
1058
+ fastify.setValidatorCompiler((routeSchemaDef) => ajv2.compile(routeSchemaDef.schema))
1059
+
1060
+ fastify.post('/two/:id', {
1061
+ schema: {
1062
+ params: {
1063
+ type: 'object',
1064
+ extended_two: true,
1065
+ properties: {
1066
+ id: { type: 'number' }
1067
+ },
1068
+ required: ['id']
1069
+ }
1070
+ },
1071
+ handler: (req, _reply) => {
1072
+ t.same(typeof req.params.id, 'number')
1073
+ t.same(req.params.id, 43)
1074
+ return 'ok'
1075
+ }
1076
+ })
1040
1077
 
1041
1078
  await fastify.ready()
1079
+
1080
+ const two = await fastify.inject({
1081
+ method: 'POST',
1082
+ url: '/two/43'
1083
+ })
1084
+ t.equal(two.statusCode, 200)
1085
+ })
1086
+
1087
+ test('Custom validator builder override by custom validator compiler in child instance', async t => {
1088
+ t.plan(6)
1089
+ const ajvDefaults = {
1090
+ removeAdditional: true,
1091
+ coerceTypes: true,
1092
+ allErrors: true
1093
+ }
1094
+ const ajv1 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_one', type: 'object', validator: () => true })
1095
+ const ajv2 = new AJV(ajvDefaults).addKeyword({ keyword: 'extended_two', type: 'object', validator: () => true })
1096
+ const fastify = Fastify({ schemaController: { compilersFactory: { buildValidator: () => (routeSchemaDef) => ajv1.compile(routeSchemaDef.schema) } } })
1097
+
1098
+ fastify.register((embedded, _opts, done) => {
1099
+ embedded.setValidatorCompiler((routeSchemaDef) => ajv2.compile(routeSchemaDef.schema))
1100
+ embedded.post('/two/:id', {
1101
+ schema: {
1102
+ params: {
1103
+ type: 'object',
1104
+ extended_two: true,
1105
+ properties: {
1106
+ id: { type: 'number' }
1107
+ },
1108
+ required: ['id']
1109
+ }
1110
+ },
1111
+ handler: (req, _reply) => {
1112
+ t.same(typeof req.params.id, 'number')
1113
+ t.same(req.params.id, 43)
1114
+ return 'ok'
1115
+ }
1116
+ })
1117
+ done()
1118
+ })
1119
+
1120
+ fastify.post('/one/:id', {
1121
+ schema: {
1122
+ params: {
1123
+ type: 'object',
1124
+ extended_one: true,
1125
+ properties: {
1126
+ id: { type: 'number' }
1127
+ },
1128
+ required: ['id']
1129
+ }
1130
+ },
1131
+ handler: (req, _reply) => {
1132
+ t.same(typeof req.params.id, 'number')
1133
+ t.same(req.params.id, 42)
1134
+ return 'ok'
1135
+ }
1136
+ })
1137
+
1138
+ await fastify.ready()
1139
+
1140
+ const one = await fastify.inject({
1141
+ method: 'POST',
1142
+ url: '/one/42'
1143
+ })
1144
+ t.equal(one.statusCode, 200)
1145
+
1146
+ const two = await fastify.inject({
1147
+ method: 'POST',
1148
+ url: '/two/43'
1149
+ })
1150
+ t.equal(two.statusCode, 200)
1042
1151
  })