fastify 5.8.4 → 5.9.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 (105) hide show
  1. package/SECURITY.md +1 -1
  2. package/SPONSORS.md +6 -4
  3. package/docs/Guides/Database.md +0 -28
  4. package/docs/Guides/Ecosystem.md +17 -2
  5. package/docs/Guides/Serverless.md +2 -2
  6. package/docs/Guides/Write-Plugin.md +1 -1
  7. package/docs/Reference/Encapsulation.md +27 -26
  8. package/docs/Reference/Errors.md +10 -4
  9. package/docs/Reference/HTTP2.md +10 -10
  10. package/docs/Reference/Hooks.md +4 -4
  11. package/docs/Reference/Index.md +14 -16
  12. package/docs/Reference/LTS.md +12 -13
  13. package/docs/Reference/Lifecycle.md +9 -8
  14. package/docs/Reference/Logging.md +44 -39
  15. package/docs/Reference/Middleware.md +21 -25
  16. package/docs/Reference/Principles.md +2 -2
  17. package/docs/Reference/Reply.md +6 -1
  18. package/docs/Reference/Request.md +27 -16
  19. package/docs/Reference/Routes.md +5 -2
  20. package/docs/Reference/Server.md +31 -3
  21. package/docs/Reference/Type-Providers.md +29 -5
  22. package/docs/Reference/Validation-and-Serialization.md +15 -2
  23. package/docs/Reference/Warnings.md +7 -6
  24. package/eslint.config.js +7 -2
  25. package/fastify.d.ts +8 -3
  26. package/fastify.js +43 -14
  27. package/lib/content-type-parser.js +13 -1
  28. package/lib/decorate.js +11 -3
  29. package/lib/error-handler.js +4 -3
  30. package/lib/error-serializer.js +59 -59
  31. package/lib/errors.js +16 -1
  32. package/lib/four-oh-four.js +14 -9
  33. package/lib/handle-request.js +11 -5
  34. package/lib/plugin-override.js +2 -1
  35. package/lib/plugin-utils.js +5 -5
  36. package/lib/reply.js +63 -8
  37. package/lib/request.js +23 -19
  38. package/lib/route.js +20 -6
  39. package/lib/schema-controller.js +1 -1
  40. package/lib/schemas.js +37 -30
  41. package/lib/symbols.js +3 -1
  42. package/lib/validation.js +1 -13
  43. package/lib/warnings.js +3 -3
  44. package/package.json +16 -18
  45. package/scripts/validate-ecosystem-links.js +1 -0
  46. package/test/bundler/esbuild/package.json +1 -1
  47. package/test/close-pipelining.test.js +1 -2
  48. package/test/custom-http-server.test.js +38 -0
  49. package/test/decorator-instance-properties.test.js +63 -0
  50. package/test/diagnostics-channel/async-error-handler.test.js +74 -0
  51. package/test/hooks.test.js +23 -0
  52. package/test/http-methods/get.test.js +1 -1
  53. package/test/http2/plain.test.js +135 -0
  54. package/test/http2/secure-with-fallback.test.js +1 -1
  55. package/test/https/https.test.js +1 -2
  56. package/test/internals/errors.test.js +31 -1
  57. package/test/internals/plugin.test.js +3 -1
  58. package/test/internals/request.test.js +27 -3
  59. package/test/internals/schema-controller-perf.test.js +33 -0
  60. package/test/logger/logging.test.js +18 -1
  61. package/test/logger/options.test.js +38 -1
  62. package/test/reply-error.test.js +1 -1
  63. package/test/reply-trailers.test.js +70 -0
  64. package/test/request-media-type.test.js +105 -0
  65. package/test/request-port.test.js +72 -0
  66. package/test/route-prefix.test.js +34 -0
  67. package/test/router-options.test.js +222 -11
  68. package/test/schema-serialization.test.js +108 -0
  69. package/test/schema-validation.test.js +45 -0
  70. package/test/scripts/validate-ecosystem-links.test.js +40 -57
  71. package/test/throw.test.js +14 -0
  72. package/test/trust-proxy.test.js +74 -0
  73. package/test/types/content-type-parser.tst.ts +70 -0
  74. package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
  75. package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
  76. package/test/types/errors.tst.ts +91 -0
  77. package/test/types/fastify.tst.ts +351 -0
  78. package/test/types/hooks.tst.ts +578 -0
  79. package/test/types/instance.tst.ts +597 -0
  80. package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
  81. package/test/types/plugin.tst.ts +96 -0
  82. package/test/types/register.tst.ts +245 -0
  83. package/test/types/reply.tst.ts +297 -0
  84. package/test/types/request.tst.ts +199 -0
  85. package/test/types/route.tst.ts +576 -0
  86. package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
  87. package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
  88. package/test/types/tsconfig.json +9 -0
  89. package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
  90. package/test/types/using.tst.ts +14 -0
  91. package/types/errors.d.ts +3 -0
  92. package/types/request.d.ts +23 -2
  93. package/AGENTS.md +0 -290
  94. package/test/types/content-type-parser.test-d.ts +0 -72
  95. package/test/types/errors.test-d.ts +0 -90
  96. package/test/types/fastify.test-d.ts +0 -352
  97. package/test/types/hooks.test-d.ts +0 -550
  98. package/test/types/import.ts +0 -2
  99. package/test/types/instance.test-d.ts +0 -588
  100. package/test/types/plugin.test-d.ts +0 -97
  101. package/test/types/register.test-d.ts +0 -237
  102. package/test/types/reply.test-d.ts +0 -254
  103. package/test/types/request.test-d.ts +0 -188
  104. package/test/types/route.test-d.ts +0 -553
  105. package/test/types/using.test-d.ts +0 -17
@@ -192,6 +192,46 @@ test('error in trailers should be ignored', (t, testDone) => {
192
192
  })
193
193
  })
194
194
 
195
+ test('send is called once when multiple trailer callbacks run synchronously', (t, testDone) => {
196
+ t.plan(6)
197
+ const fastify = Fastify()
198
+ let endCalls = 0
199
+ let addTrailersCalls = 0
200
+
201
+ fastify.get('/', function (request, reply) {
202
+ const originalEnd = reply.raw.end.bind(reply.raw)
203
+ reply.raw.end = function (...args) {
204
+ endCalls++
205
+ return originalEnd(...args)
206
+ }
207
+ const originalAddTrailers = reply.raw.addTrailers.bind(reply.raw)
208
+ reply.raw.addTrailers = function (...args) {
209
+ addTrailersCalls++
210
+ return originalAddTrailers(...args)
211
+ }
212
+ reply.trailer('Return-Early', function (reply, payload, done) {
213
+ done(null, 'a')
214
+ })
215
+ reply.trailer('Content-MD5', function (reply, payload, done) {
216
+ done(null, 'b')
217
+ })
218
+ reply.send('hello')
219
+ })
220
+
221
+ fastify.inject({
222
+ method: 'GET',
223
+ url: '/'
224
+ }, (error, res) => {
225
+ t.assert.ifError(error)
226
+ t.assert.strictEqual(res.statusCode, 200)
227
+ t.assert.strictEqual(res.trailers['return-early'], 'a')
228
+ t.assert.strictEqual(res.trailers['content-md5'], 'b')
229
+ t.assert.strictEqual(endCalls, 1)
230
+ t.assert.strictEqual(addTrailersCalls, 1)
231
+ testDone()
232
+ })
233
+ })
234
+
195
235
  describe('trailer handler counter', () => {
196
236
  const data = JSON.stringify({ hello: 'world' })
197
237
  const hash = createHash('md5')
@@ -266,6 +306,36 @@ describe('trailer handler counter', () => {
266
306
  testDone()
267
307
  })
268
308
  })
309
+
310
+ test('mixed callback and promise trailers only use the first completion', (t, testDone) => {
311
+ t.plan(7)
312
+ const fastify = Fastify()
313
+
314
+ fastify.get('/', function (request, reply) {
315
+ reply.trailer('Async', function (reply, payload, done) {
316
+ setTimeout(() => done(null, 'async'), 10)
317
+ })
318
+ reply.trailer('Mixed', function (reply, payload, done) {
319
+ done(null, 'correct')
320
+ return Promise.resolve('corrupted')
321
+ })
322
+ reply.send('hello')
323
+ })
324
+
325
+ fastify.inject({
326
+ method: 'GET',
327
+ url: '/'
328
+ }, (error, res) => {
329
+ t.assert.ifError(error)
330
+ t.assert.strictEqual(res.statusCode, 200)
331
+ t.assert.strictEqual(res.headers['transfer-encoding'], 'chunked')
332
+ t.assert.strictEqual(res.headers.trailer, 'async mixed')
333
+ t.assert.strictEqual(res.trailers.async, 'async')
334
+ t.assert.strictEqual(res.trailers.mixed, 'correct')
335
+ t.assert.ok(!res.headers['content-length'])
336
+ testDone()
337
+ })
338
+ })
269
339
  })
270
340
 
271
341
  test('removeTrailer', (t, testDone) => {
@@ -0,0 +1,105 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+
6
+ test('request.mediaType should match the content-type header', async (t) => {
7
+ t.plan(2)
8
+
9
+ const fastify = Fastify()
10
+
11
+ fastify.post('/', (request, reply) => {
12
+ t.assert.strictEqual(request.mediaType, 'application/json')
13
+ reply.send({ mediaType: request.mediaType })
14
+ })
15
+
16
+ const response = await fastify.inject({
17
+ method: 'POST',
18
+ url: '/',
19
+ body: JSON.stringify({ hello: 'world' }),
20
+ headers: {
21
+ 'content-type': 'application/json'
22
+ }
23
+ })
24
+ const body = await response.json()
25
+ t.assert.strictEqual(body.mediaType, 'application/json')
26
+ })
27
+
28
+ test('request.mediaType should strip the charset parameter', async (t) => {
29
+ t.plan(2)
30
+
31
+ const fastify = Fastify()
32
+
33
+ fastify.post('/', (request, reply) => {
34
+ t.assert.strictEqual(request.mediaType, 'application/json')
35
+ reply.send({ mediaType: request.mediaType })
36
+ })
37
+
38
+ const response = await fastify.inject({
39
+ method: 'POST',
40
+ url: '/',
41
+ body: JSON.stringify({ hello: 'world' }),
42
+ headers: {
43
+ 'content-type': 'application/json; charset=utf-8'
44
+ }
45
+ })
46
+ const body = await response.json()
47
+ t.assert.strictEqual(body.mediaType, 'application/json')
48
+ })
49
+
50
+ test('request.mediaType should strip the space', async (t) => {
51
+ t.plan(2)
52
+
53
+ const fastify = Fastify()
54
+
55
+ fastify.post('/', (request, reply) => {
56
+ t.assert.strictEqual(request.mediaType, 'application/json')
57
+ reply.send({ mediaType: request.mediaType })
58
+ })
59
+
60
+ const response = await fastify.inject({
61
+ method: 'POST',
62
+ url: '/',
63
+ body: JSON.stringify({ hello: 'world' }),
64
+ headers: {
65
+ 'content-type': ' application/json ; charset=utf-8'
66
+ }
67
+ })
68
+ const body = await response.json()
69
+ t.assert.strictEqual(body.mediaType, 'application/json')
70
+ })
71
+
72
+ test('request.mediaType supported in hooks', async (t) => {
73
+ t.plan(5)
74
+
75
+ const fastify = Fastify()
76
+
77
+ fastify.post('/', {
78
+ preParsing: (request, reply, payload, done) => {
79
+ t.assert.strictEqual(request.mediaType, 'application/json')
80
+ done(null, payload)
81
+ },
82
+ preValidation: (request, reply, done) => {
83
+ t.assert.strictEqual(request.mediaType, 'application/json')
84
+ done()
85
+ },
86
+ preHandler: (request, reply, done) => {
87
+ t.assert.strictEqual(request.mediaType, 'application/json')
88
+ done()
89
+ }
90
+ }, (request, reply) => {
91
+ t.assert.strictEqual(request.mediaType, 'application/json')
92
+ reply.send({ mediaType: request.mediaType })
93
+ })
94
+
95
+ const response = await fastify.inject({
96
+ method: 'POST',
97
+ url: '/',
98
+ body: JSON.stringify({ hello: 'world' }),
99
+ headers: {
100
+ 'content-type': 'application/json'
101
+ }
102
+ })
103
+ const body = await response.json()
104
+ t.assert.strictEqual(body.mediaType, 'application/json')
105
+ })
@@ -0,0 +1,72 @@
1
+ 'use strict'
2
+
3
+ const test = require('node:test')
4
+ const Request = require('../lib/request')
5
+
6
+ test('.port parses port correctly', (t) => {
7
+ const fixtures = [
8
+ {
9
+ expected: 80,
10
+ req: {
11
+ headers: {
12
+ host: 'example.com:80'
13
+ }
14
+ }
15
+ },
16
+ {
17
+ expected: 443,
18
+ req: {
19
+ headers: {
20
+ host: 'example.com:443'
21
+ }
22
+ }
23
+ },
24
+ {
25
+ expected: 80,
26
+ req: {
27
+ headers: {
28
+ host: '[::1]:80'
29
+ }
30
+ }
31
+ },
32
+ {
33
+ expected: 443,
34
+ req: {
35
+ headers: {
36
+ host: '[::1]:443'
37
+ }
38
+ }
39
+ },
40
+ {
41
+ expected: null,
42
+ req: {
43
+ headers: {
44
+ host: '[::1]'
45
+ }
46
+ }
47
+ },
48
+ {
49
+ expected: 80,
50
+ req: {
51
+ headers: {
52
+ ':authority': '1.2.3.4:80'
53
+ }
54
+ }
55
+ },
56
+ {
57
+ expected: null,
58
+ req: {
59
+ headers: {}
60
+ }
61
+ }
62
+ ]
63
+
64
+ for (const fixture of fixtures) {
65
+ const req = new Request(1, {}, fixture.req, '', {}, {})
66
+ t.assert.equal(
67
+ req.port,
68
+ fixture.expected,
69
+ `${fixture.req.headers.host} should parse to ${fixture.expected}`
70
+ )
71
+ }
72
+ })
@@ -241,6 +241,40 @@ test('Prefix with trailing /', (t, testDone) => {
241
241
  completion.patience.then(testDone)
242
242
  })
243
243
 
244
+ test('Prefix with trailing / and nested prefix without leading /', (t, testDone) => {
245
+ t.plan(4)
246
+ const fastify = Fastify()
247
+
248
+ fastify.register(function (fastify, opts, done) {
249
+ fastify.register(function (fastify, opts, done) {
250
+ fastify.get('/route', (req, reply) => {
251
+ reply.send({ hello: 'world' })
252
+ })
253
+ done()
254
+ }, { prefix: 'inner' })
255
+ done()
256
+ }, { prefix: '/v1/' })
257
+
258
+ const completion = waitForCb({ steps: 2 })
259
+ fastify.inject({
260
+ method: 'GET',
261
+ url: '/v1/inner/route'
262
+ }, (err, res) => {
263
+ t.assert.ifError(err)
264
+ t.assert.deepStrictEqual(JSON.parse(res.payload), { hello: 'world' })
265
+ completion.stepIn()
266
+ })
267
+ fastify.inject({
268
+ method: 'GET',
269
+ url: '/v1//inner/route'
270
+ }, (err, res) => {
271
+ t.assert.ifError(err)
272
+ t.assert.strictEqual(res.statusCode, 404)
273
+ completion.stepIn()
274
+ })
275
+ completion.patience.then(testDone)
276
+ })
277
+
244
278
  test('Prefix works multiple levels deep', (t, testDone) => {
245
279
  t.plan(2)
246
280
  const fastify = Fastify()
@@ -6,6 +6,7 @@ const querystring = require('node:querystring')
6
6
  const Fastify = require('../')
7
7
  const {
8
8
  FST_ERR_BAD_URL,
9
+ FST_ERR_MAX_PARAM_LENGTH,
9
10
  FST_ERR_ASYNC_CONSTRAINT
10
11
  } = require('../lib/errors')
11
12
 
@@ -84,7 +85,7 @@ test('Should honor maxParamLength option', async (t) => {
84
85
  method: 'GET',
85
86
  url: '/test/123456789abcd'
86
87
  })
87
- t.assert.strictEqual(resError.statusCode, 404)
88
+ t.assert.strictEqual(resError.statusCode, 414)
88
89
  })
89
90
 
90
91
  test('Should expose router options via getters on request and reply', (t, done) => {
@@ -262,6 +263,130 @@ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST
262
263
  )
263
264
  })
264
265
 
266
+ test('Should honor frameworkErrors option - FST_ERR_MAX_PARAM_LENGTH', (t, done) => {
267
+ t.plan(3)
268
+ const fastify = Fastify({
269
+ maxParamLength: 1,
270
+ frameworkErrors: function (err, req, res) {
271
+ if (err instanceof FST_ERR_MAX_PARAM_LENGTH) {
272
+ t.assert.ok(true)
273
+ } else {
274
+ t.assert.fail()
275
+ }
276
+ res.send(`${err.message} - ${err.code}`)
277
+ }
278
+ })
279
+
280
+ fastify.get('/test/:id', (req, res) => {
281
+ res.send('{ hello: \'world\' }')
282
+ })
283
+
284
+ fastify.inject(
285
+ {
286
+ method: 'GET',
287
+ url: '/test/123'
288
+ },
289
+ (err, res) => {
290
+ t.assert.ifError(err)
291
+ t.assert.strictEqual(res.body, '\'/test/123\' is exceeding the max param length - FST_ERR_MAX_PARAM_LENGTH')
292
+ done()
293
+ }
294
+ )
295
+ })
296
+
297
+ test('Should supply Fastify request to the logger in frameworkErrors wrapper - FST_ERR_MAX_PARAM_LENGTH', (t, done) => {
298
+ t.plan(8)
299
+
300
+ const REQ_ID = 'REQ-1234'
301
+ const logStream = split(JSON.parse)
302
+
303
+ const fastify = Fastify({
304
+ maxParamLength: 1,
305
+ frameworkErrors: function (err, req, res) {
306
+ t.assert.deepStrictEqual(req.id, REQ_ID)
307
+ t.assert.deepStrictEqual(req.raw.httpVersion, '1.1')
308
+ res.send(`${err.message} - ${err.code}`)
309
+ },
310
+ logger: {
311
+ stream: logStream,
312
+ serializers: {
313
+ req (request) {
314
+ t.assert.deepStrictEqual(request.id, REQ_ID)
315
+ return { httpVersion: request.raw.httpVersion }
316
+ }
317
+ }
318
+ },
319
+ genReqId: () => REQ_ID
320
+ })
321
+
322
+ fastify.get('/test/:id', (req, res) => {
323
+ res.send('{ hello: \'world\' }')
324
+ })
325
+
326
+ logStream.on('data', (json) => {
327
+ t.assert.deepStrictEqual(json.msg, 'incoming request')
328
+ t.assert.deepStrictEqual(json.reqId, REQ_ID)
329
+ t.assert.deepStrictEqual(json.req.httpVersion, '1.1')
330
+ })
331
+
332
+ fastify.inject(
333
+ {
334
+ method: 'GET',
335
+ url: '/test/123'
336
+ },
337
+ (err, res) => {
338
+ t.assert.ifError(err)
339
+ t.assert.strictEqual(res.body, '\'/test/123\' is exceeding the max param length - FST_ERR_MAX_PARAM_LENGTH')
340
+ done()
341
+ }
342
+ )
343
+ })
344
+
345
+ test('Should honor disableRequestLogging option in frameworkErrors wrapper - FST_ERR_MAX_PARAM_LENGTH', (t, done) => {
346
+ t.plan(2)
347
+
348
+ const logStream = split(JSON.parse)
349
+
350
+ const fastify = Fastify({
351
+ disableRequestLogging: true,
352
+ maxParamLength: 1,
353
+ frameworkErrors: function (err, req, res) {
354
+ res.send(`${err.message} - ${err.code}`)
355
+ },
356
+ logger: {
357
+ stream: logStream,
358
+ serializers: {
359
+ req () {
360
+ t.assert.fail('should not be called')
361
+ },
362
+ res () {
363
+ t.assert.fail('should not be called')
364
+ }
365
+ }
366
+ }
367
+ })
368
+
369
+ fastify.get('/test/:id', (req, res) => {
370
+ res.send('{ hello: \'world\' }')
371
+ })
372
+
373
+ logStream.on('data', (json) => {
374
+ t.assert.fail('should not be called')
375
+ })
376
+
377
+ fastify.inject(
378
+ {
379
+ method: 'GET',
380
+ url: '/test/123'
381
+ },
382
+ (err, res) => {
383
+ t.assert.ifError(err)
384
+ t.assert.strictEqual(res.body, '\'/test/123\' is exceeding the max param length - FST_ERR_MAX_PARAM_LENGTH')
385
+ done()
386
+ }
387
+ )
388
+ })
389
+
265
390
  test('Should honor frameworkErrors option - FST_ERR_ASYNC_CONSTRAINT', (t, done) => {
266
391
  t.plan(3)
267
392
 
@@ -507,6 +632,67 @@ test('Should honor disableRequestLogging function in frameworkErrors wrapper - F
507
632
  )
508
633
  })
509
634
 
635
+ test('Should honor disableRequestLogging function in frameworkErrors wrapper - FST_ERR_MAX_PARAM_LENGTH', (t, done) => {
636
+ t.plan(4)
637
+
638
+ let logCallCount = 0
639
+ const logStream = split(JSON.parse)
640
+
641
+ const fastify = Fastify({
642
+ maxParamLength: 1,
643
+ disableRequestLogging: (req) => {
644
+ // Disable logging for URLs containing 'silent'
645
+ return req.url.includes('silent')
646
+ },
647
+ frameworkErrors: function (err, req, res) {
648
+ res.send(`${err.message} - ${err.code}`)
649
+ },
650
+ logger: {
651
+ stream: logStream,
652
+ level: 'info'
653
+ }
654
+ })
655
+
656
+ fastify.get('/test/:id', (req, res) => {
657
+ res.send('{ hello: \'world\' }')
658
+ })
659
+
660
+ logStream.on('data', (json) => {
661
+ if (json.msg === 'incoming request') {
662
+ logCallCount++
663
+ }
664
+ })
665
+
666
+ // First request: URL does not contain 'silent', so logging should happen
667
+ fastify.inject(
668
+ {
669
+ method: 'GET',
670
+ url: '/test/123'
671
+ },
672
+ (err, res) => {
673
+ t.assert.ifError(err)
674
+ t.assert.strictEqual(res.body, '\'/test/123\' is exceeding the max param length - FST_ERR_MAX_PARAM_LENGTH')
675
+
676
+ // Second request: URL contains 'silent', so logging should be disabled
677
+ fastify.inject(
678
+ {
679
+ method: 'GET',
680
+ url: '/silent/123'
681
+ },
682
+ (err2, res2) => {
683
+ t.assert.ifError(err2)
684
+ // Give time for any potential log events
685
+ setImmediate(() => {
686
+ // Only the first request should have logged
687
+ t.assert.strictEqual(logCallCount, 1)
688
+ done()
689
+ })
690
+ }
691
+ )
692
+ }
693
+ )
694
+ })
695
+
510
696
  test('Should honor disableRequestLogging function in frameworkErrors wrapper - FST_ERR_ASYNC_CONSTRAINT', (t, done) => {
511
697
  t.plan(4)
512
698
 
@@ -615,7 +801,7 @@ test('Should honor routerOptions.defaultRoute', async t => {
615
801
  t.assert.strictEqual(res.payload, 'default route')
616
802
  })
617
803
 
618
- test('Should honor routerOptions.badUrl', async t => {
804
+ test('Should honor routerOptions.onBadUrl', async t => {
619
805
  t.plan(3)
620
806
  const fastify = Fastify({
621
807
  routerOptions: {
@@ -625,7 +811,7 @@ test('Should honor routerOptions.badUrl', async t => {
625
811
  onBadUrl: function (path, _, res) {
626
812
  t.assert.ok('bad url called')
627
813
  res.statusCode = 400
628
- res.end(`Bath URL: ${path}`)
814
+ res.end(`Bad URL: ${path}`)
629
815
  }
630
816
  }
631
817
  })
@@ -636,7 +822,32 @@ test('Should honor routerOptions.badUrl', async t => {
636
822
 
637
823
  const res = await fastify.inject('/hello/%world')
638
824
  t.assert.strictEqual(res.statusCode, 400)
639
- t.assert.strictEqual(res.payload, 'Bath URL: /hello/%world')
825
+ t.assert.strictEqual(res.payload, 'Bad URL: /hello/%world')
826
+ })
827
+
828
+ test('Should honor routerOptions.onMaxParamLength', async t => {
829
+ t.plan(3)
830
+ const fastify = Fastify({
831
+ routerOptions: {
832
+ defaultRoute: function (_, res) {
833
+ t.asset.fail('default route should not be called')
834
+ },
835
+ maxParamLength: 10,
836
+ onMaxParamLength: function (path, _, res) {
837
+ t.assert.ok('max param length called')
838
+ res.statusCode = 414
839
+ res.end(`URL: ${path}`)
840
+ }
841
+ }
842
+ })
843
+
844
+ fastify.get('/hello/:id', (req, res) => {
845
+ res.send({ hello: 'world' })
846
+ })
847
+
848
+ const res = await fastify.inject('/hello/12345678901')
849
+ t.assert.strictEqual(res.statusCode, 414)
850
+ t.assert.strictEqual(res.payload, 'URL: /hello/12345678901')
640
851
  })
641
852
 
642
853
  test('Should honor routerOptions.ignoreTrailingSlash', async t => {
@@ -727,7 +938,7 @@ test('Should honor routerOptions.maxParamLength', async (t) => {
727
938
  method: 'GET',
728
939
  url: '/test/123456789abcd'
729
940
  })
730
- t.assert.strictEqual(resError.statusCode, 404)
941
+ t.assert.strictEqual(resError.statusCode, 414)
731
942
  })
732
943
 
733
944
  test('Should honor routerOptions.allowUnsafeRegex', async (t) => {
@@ -854,11 +1065,11 @@ test('Should honor routerOptions.buildPrettyMeta', async (t) => {
854
1065
  }
855
1066
  })
856
1067
 
857
- fastify.get('/test', () => {})
858
- fastify.get('/test/hello', () => {})
859
- fastify.get('/testing', () => {})
860
- fastify.get('/testing/:param', () => {})
861
- fastify.put('/update', () => {})
1068
+ fastify.get('/test', () => { })
1069
+ fastify.get('/test/hello', () => { })
1070
+ fastify.get('/testing', () => { })
1071
+ fastify.get('/testing/:param', () => { })
1072
+ fastify.put('/update', () => { })
862
1073
 
863
1074
  await fastify.ready()
864
1075
 
@@ -928,7 +1139,7 @@ test('Should honor routerOptions.maxParamLength over maxParamLength option', asy
928
1139
  method: 'GET',
929
1140
  url: '/test/123456789abcd'
930
1141
  })
931
- t.assert.strictEqual(resError.statusCode, 404)
1142
+ t.assert.strictEqual(resError.statusCode, 414)
932
1143
  })
933
1144
 
934
1145
  test('Should honor routerOptions.allowUnsafeRegex over allowUnsafeRegex option', async (t) => {
@@ -1169,3 +1169,111 @@ test('allow default as status code and used as last fallback', (t, testDone) =>
1169
1169
  testDone()
1170
1170
  })
1171
1171
  })
1172
+
1173
+ test('response schema is applied when Content-Type has whitespace before parameters', (t, testDone) => {
1174
+ t.plan(3)
1175
+
1176
+ const fastify = Fastify()
1177
+ fastify.get('/', {
1178
+ schema: {
1179
+ response: {
1180
+ 200: {
1181
+ content: {
1182
+ 'application/json': {
1183
+ schema: {
1184
+ type: 'object',
1185
+ required: ['public'],
1186
+ additionalProperties: false,
1187
+ properties: {
1188
+ public: { type: 'string' }
1189
+ }
1190
+ }
1191
+ }
1192
+ }
1193
+ }
1194
+ }
1195
+ }
1196
+ }, function (req, reply) {
1197
+ reply.header('Content-Type', 'application/json ; charset=utf-8')
1198
+ reply.send({ public: 'ok', secret: 'SHOULD_BE_STRIPPED' })
1199
+ })
1200
+
1201
+ fastify.inject('/', (err, res) => {
1202
+ t.assert.ifError(err)
1203
+ t.assert.strictEqual(res.statusCode, 200)
1204
+ t.assert.deepStrictEqual(res.json(), { public: 'ok' })
1205
+ testDone()
1206
+ })
1207
+ })
1208
+
1209
+ test('response schema is applied when Content-Type has tab before semicolon', (t, testDone) => {
1210
+ t.plan(3)
1211
+
1212
+ const fastify = Fastify()
1213
+ fastify.get('/', {
1214
+ schema: {
1215
+ response: {
1216
+ 200: {
1217
+ content: {
1218
+ 'application/json': {
1219
+ schema: {
1220
+ type: 'object',
1221
+ required: ['public'],
1222
+ additionalProperties: false,
1223
+ properties: {
1224
+ public: { type: 'string' }
1225
+ }
1226
+ }
1227
+ }
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+ }, function (req, reply) {
1233
+ reply.header('Content-Type', 'application/json\t; charset=utf-8')
1234
+ reply.send({ public: 'ok', secret: 'SHOULD_BE_STRIPPED' })
1235
+ })
1236
+
1237
+ fastify.inject('/', (err, res) => {
1238
+ t.assert.ifError(err)
1239
+ t.assert.strictEqual(res.statusCode, 200)
1240
+ t.assert.deepStrictEqual(res.json(), { public: 'ok' })
1241
+ testDone()
1242
+ })
1243
+ })
1244
+
1245
+ test('response schema is applied when Content-Type has trailing semicolon only', (t, testDone) => {
1246
+ t.plan(3)
1247
+
1248
+ const fastify = Fastify()
1249
+ fastify.get('/', {
1250
+ schema: {
1251
+ response: {
1252
+ 200: {
1253
+ content: {
1254
+ 'application/json': {
1255
+ schema: {
1256
+ type: 'object',
1257
+ required: ['public'],
1258
+ additionalProperties: false,
1259
+ properties: {
1260
+ public: { type: 'string' }
1261
+ }
1262
+ }
1263
+ }
1264
+ }
1265
+ }
1266
+ }
1267
+ }
1268
+ }, function (req, reply) {
1269
+ reply.header('Content-Type', 'application/json ;')
1270
+ reply.send({ public: 'ok', secret: 'SHOULD_BE_STRIPPED' })
1271
+ })
1272
+
1273
+ fastify.inject('/', (err, res) => {
1274
+ t.assert.ifError(err)
1275
+ t.assert.strictEqual(res.statusCode, 200)
1276
+ t.assert.deepStrictEqual(res.json(), { public: 'ok' })
1277
+ testDone()
1278
+ })
1279
+ })