fastify 5.8.5 → 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.
- package/SECURITY.md +1 -1
- package/SPONSORS.md +6 -4
- package/docs/Guides/Database.md +0 -28
- package/docs/Guides/Ecosystem.md +13 -2
- package/docs/Guides/Serverless.md +2 -2
- package/docs/Guides/Write-Plugin.md +1 -1
- package/docs/Reference/Encapsulation.md +27 -26
- package/docs/Reference/Errors.md +10 -4
- package/docs/Reference/HTTP2.md +10 -10
- package/docs/Reference/Hooks.md +4 -4
- package/docs/Reference/Index.md +14 -16
- package/docs/Reference/LTS.md +12 -13
- package/docs/Reference/Lifecycle.md +9 -8
- package/docs/Reference/Logging.md +44 -39
- package/docs/Reference/Middleware.md +21 -25
- package/docs/Reference/Principles.md +2 -2
- package/docs/Reference/Reply.md +6 -1
- package/docs/Reference/Request.md +27 -16
- package/docs/Reference/Routes.md +5 -2
- package/docs/Reference/Server.md +31 -3
- package/docs/Reference/Type-Providers.md +29 -5
- package/docs/Reference/Validation-and-Serialization.md +15 -2
- package/docs/Reference/Warnings.md +7 -6
- package/eslint.config.js +7 -2
- package/fastify.d.ts +8 -3
- package/fastify.js +43 -14
- package/lib/content-type-parser.js +13 -1
- package/lib/decorate.js +11 -3
- package/lib/error-handler.js +4 -3
- package/lib/error-serializer.js +59 -59
- package/lib/errors.js +16 -1
- package/lib/four-oh-four.js +14 -9
- package/lib/handle-request.js +11 -5
- package/lib/plugin-override.js +2 -1
- package/lib/plugin-utils.js +5 -5
- package/lib/reply.js +63 -8
- package/lib/request.js +14 -4
- package/lib/route.js +20 -6
- package/lib/schema-controller.js +1 -1
- package/lib/schemas.js +37 -30
- package/lib/symbols.js +3 -1
- package/lib/validation.js +1 -13
- package/lib/warnings.js +3 -3
- package/package.json +13 -15
- package/scripts/validate-ecosystem-links.js +1 -0
- package/test/bundler/esbuild/package.json +1 -1
- package/test/close-pipelining.test.js +1 -2
- package/test/custom-http-server.test.js +38 -0
- package/test/decorator-instance-properties.test.js +63 -0
- package/test/diagnostics-channel/async-error-handler.test.js +74 -0
- package/test/hooks.test.js +23 -0
- package/test/http-methods/get.test.js +1 -1
- package/test/http2/plain.test.js +135 -0
- package/test/http2/secure-with-fallback.test.js +1 -1
- package/test/https/https.test.js +1 -2
- package/test/internals/errors.test.js +31 -1
- package/test/internals/plugin.test.js +3 -1
- package/test/internals/request.test.js +27 -3
- package/test/internals/schema-controller-perf.test.js +33 -0
- package/test/logger/logging.test.js +18 -1
- package/test/logger/options.test.js +38 -1
- package/test/reply-error.test.js +1 -1
- package/test/reply-trailers.test.js +70 -0
- package/test/request-media-type.test.js +105 -0
- package/test/route-prefix.test.js +34 -0
- package/test/router-options.test.js +222 -11
- package/test/schema-serialization.test.js +108 -0
- package/test/schema-validation.test.js +24 -0
- package/test/scripts/validate-ecosystem-links.test.js +40 -57
- package/test/throw.test.js +14 -0
- package/test/trust-proxy.test.js +21 -0
- package/test/types/content-type-parser.tst.ts +70 -0
- package/test/types/{decorate-request-reply.test-d.ts → decorate-request-reply.tst.ts} +4 -4
- package/test/types/{dummy-plugin.ts → dummy-plugin.mts} +1 -1
- package/test/types/errors.tst.ts +91 -0
- package/test/types/fastify.tst.ts +351 -0
- package/test/types/hooks.tst.ts +578 -0
- package/test/types/instance.tst.ts +597 -0
- package/test/types/{logger.test-d.ts → logger.tst.ts} +61 -62
- package/test/types/plugin.tst.ts +96 -0
- package/test/types/register.tst.ts +245 -0
- package/test/types/reply.tst.ts +297 -0
- package/test/types/request.tst.ts +199 -0
- package/test/types/route.tst.ts +576 -0
- package/test/types/{schema.test-d.ts → schema.tst.ts} +22 -22
- package/test/types/{serverFactory.test-d.ts → serverFactory.tst.ts} +4 -4
- package/test/types/tsconfig.json +9 -0
- package/test/types/{type-provider.test-d.ts → type-provider.tst.ts} +256 -250
- package/test/types/using.tst.ts +14 -0
- package/types/errors.d.ts +3 -0
- package/types/request.d.ts +23 -2
- package/test/types/content-type-parser.test-d.ts +0 -72
- package/test/types/errors.test-d.ts +0 -90
- package/test/types/fastify.test-d.ts +0 -352
- package/test/types/hooks.test-d.ts +0 -550
- package/test/types/import.ts +0 -2
- package/test/types/instance.test-d.ts +0 -588
- package/test/types/plugin.test-d.ts +0 -97
- package/test/types/register.test-d.ts +0 -237
- package/test/types/reply.test-d.ts +0 -254
- package/test/types/request.test-d.ts +0 -188
- package/test/types/route.test-d.ts +0 -553
- package/test/types/using.test-d.ts +0 -17
|
@@ -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
|
+
})
|
|
@@ -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,
|
|
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.
|
|
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(`
|
|
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, '
|
|
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,
|
|
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,
|
|
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
|
+
})
|
|
@@ -6,6 +6,7 @@ const Fastify = require('..')
|
|
|
6
6
|
const AJV = require('ajv')
|
|
7
7
|
const Schema = require('fluent-json-schema')
|
|
8
8
|
const { waitForCb } = require('./toolkit')
|
|
9
|
+
const { kRequestContentType } = require('../lib/symbols')
|
|
9
10
|
|
|
10
11
|
const customSchemaCompilers = {
|
|
11
12
|
body: new AJV({
|
|
@@ -1374,6 +1375,7 @@ test('Schema validation when no content type is provided', async t => {
|
|
|
1374
1375
|
},
|
|
1375
1376
|
preValidation: async (request) => {
|
|
1376
1377
|
request.headers['content-type'] = undefined
|
|
1378
|
+
request[kRequestContentType] = undefined
|
|
1377
1379
|
}
|
|
1378
1380
|
}, async () => 'ok')
|
|
1379
1381
|
|
|
@@ -1591,3 +1593,25 @@ test('Schema validation will not be bypass by different content type', async t =
|
|
|
1591
1593
|
t.assert.strictEqual(found.status, 415)
|
|
1592
1594
|
t.assert.strictEqual((await found.json()).code, 'FST_ERR_CTP_INVALID_MEDIA_TYPE')
|
|
1593
1595
|
})
|
|
1596
|
+
test('coercion of empty string to null with nullable types', async t => {
|
|
1597
|
+
const assert = require('node:assert')
|
|
1598
|
+
const fastify = Fastify()
|
|
1599
|
+
fastify.get('/', {
|
|
1600
|
+
schema: {
|
|
1601
|
+
querystring: {
|
|
1602
|
+
type: 'object',
|
|
1603
|
+
properties: {
|
|
1604
|
+
param: { type: ['integer', 'null'] }
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}, async (req, reply) => {
|
|
1609
|
+
return { param: req.query.param }
|
|
1610
|
+
})
|
|
1611
|
+
|
|
1612
|
+
const res = await fastify.inject({
|
|
1613
|
+
method: 'GET',
|
|
1614
|
+
url: '/?param='
|
|
1615
|
+
})
|
|
1616
|
+
assert.strictEqual(JSON.parse(res.payload).param, null)
|
|
1617
|
+
})
|