From 0e5c11d68618ffd69cd38471acaf1a4c14d09e50 Mon Sep 17 00:00:00 2001 From: id_minaev Date: Wed, 14 May 2025 01:17:01 +0400 Subject: [PATCH 1/6] feat: add per route busboy configuration --- index.js | 2 +- types/index.d.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 4e2d1b33..27a73736 100644 --- a/index.js +++ b/index.js @@ -72,7 +72,7 @@ function fastifyMultipart (fastify, options, done) { return } - for await (const part of req.parts()) { + for await (const part of req.parts(req.routeOptions.config.multipart_options)) { req.body = part.fields if (part.file) { diff --git a/types/index.d.ts b/types/index.d.ts index 6d65f0a8..f2dfdd4e 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -35,6 +35,10 @@ declare module 'fastify' { interface FastifyInstance { multipartErrors: MultipartErrors; } + + interface FastifyContextConfig { + multipart_options?: Omit + } } type FastifyMultipartPlugin = FastifyPluginCallback< From 9fa4a211f93fa3c0bf9ffd76fb87257f2fa21356 Mon Sep 17 00:00:00 2001 From: id_minaev Date: Wed, 14 May 2025 14:40:48 +0400 Subject: [PATCH 2/6] test: add test for per route config with attachFieldsToBody --- index.js | 2 +- test/multipart-fileLimit.test.js | 124 +++++++++++++++++++++++++++++++ types/index.d.ts | 2 +- 3 files changed, 126 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 27a73736..6a3b209c 100644 --- a/index.js +++ b/index.js @@ -72,7 +72,7 @@ function fastifyMultipart (fastify, options, done) { return } - for await (const part of req.parts(req.routeOptions.config.multipart_options)) { + for await (const part of req.parts(req.routeOptions.config.multipartOptions)) { req.body = part.fields if (part.file) { diff --git a/test/multipart-fileLimit.test.js b/test/multipart-fileLimit.test.js index e4880749..b4710fff 100644 --- a/test/multipart-fileLimit.test.js +++ b/test/multipart-fileLimit.test.js @@ -316,3 +316,127 @@ test('should NOT throw fileSize limitation error when throwFileSizeLimit is glob t.error(error, 'request') } }) + +test('should throw fileSize limitation error when used alongside attachFieldsToBody and set request config', async function (t) { + t.plan(1) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.register(multipart, { + attachFieldsToBody: true + }) + + const randomFileBuffer = Buffer.alloc(2_000_000) + crypto.randomFillSync(randomFileBuffer) + + fastify.post('/', { + config: { + multipartOptions: { + limits: { + fileSize: 1_000_000 + } + } + } + }, async function (req, reply) { + t.fail('it should throw') + + reply.status(200).send() + }) + + await fastify.listen({ port: 0 }) + + // request + const form = new FormData() + const opts = { + hostname: '127.0.0.1', + port: fastify.server.address().port, + path: '/', + headers: form.getHeaders(), + method: 'POST' + } + + const tmpFile = 'test/random-file' + fs.writeFileSync(tmpFile, randomFileBuffer) + + const req = http.request(opts) + form.append('upload', fs.createReadStream(tmpFile)) + + form.pipe(req) + + try { + const [res] = await once(req, 'response') + t.equal(res.statusCode, 413) + res.resume() + await once(res, 'end') + + fs.unlinkSync(tmpFile) + } catch (error) { + t.error(error, 'request') + } +}) + +test('should not throw fileSize limitation error when used alongside attachFieldsToBody and set request config', async function (t) { + t.plan(4) + + const fastify = Fastify() + t.teardown(fastify.close.bind(fastify)) + + fastify.register(multipart, { + attachFieldsToBody: true + }) + + const randomFileBuffer = Buffer.alloc(900_000) + crypto.randomFillSync(randomFileBuffer) + + fastify.post('/', { + config: { + multipartOptions: { + limits: { + fileSize: 1_000_000 + } + } + } + }, async function (req, reply) { + t.ok(req.isMultipart()) + + t.same(Object.keys(req.body), ['upload']) + + const content = await req.body.upload.toBuffer() + + t.equal(content.toString(), randomFileBuffer.toString()) + + reply.status(200).send() + }) + + await fastify.listen({ port: 0 }) + + // request + const form = new FormData() + const opts = { + hostname: '127.0.0.1', + port: fastify.server.address().port, + path: '/', + headers: form.getHeaders(), + method: 'POST' + } + + const tmpFile = 'test/random-file' + fs.writeFileSync(tmpFile, randomFileBuffer) + + const req = http.request(opts) + form.append('upload', fs.createReadStream(tmpFile)) + + form.pipe(req) + + try { + const [res] = await once(req, 'response') + t.equal(res.statusCode, 200) + res.resume() + await once(res, 'end') + + fs.unlinkSync(tmpFile) + } catch (error) { + t.error(error, 'request') + } +}) diff --git a/types/index.d.ts b/types/index.d.ts index f2dfdd4e..67f7cbc0 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -37,7 +37,7 @@ declare module 'fastify' { } interface FastifyContextConfig { - multipart_options?: Omit + multipartOptions?: Omit } } From 5f3c511b44e95f64fcc36b15fa29ea879b2b060e Mon Sep 17 00:00:00 2001 From: id_minaev Date: Fri, 19 Sep 2025 23:11:21 +0400 Subject: [PATCH 3/6] test: migrate from tap to node multipart-fileLimit.test.js --- test/multipart-fileLimit.test.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/multipart-fileLimit.test.js b/test/multipart-fileLimit.test.js index 4dccfd72..81a23f2a 100644 --- a/test/multipart-fileLimit.test.js +++ b/test/multipart-fileLimit.test.js @@ -321,7 +321,7 @@ test('should throw fileSize limitation error when used alongside attachFieldsToB t.plan(1) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(multipart, { attachFieldsToBody: true @@ -366,13 +366,13 @@ test('should throw fileSize limitation error when used alongside attachFieldsToB try { const [res] = await once(req, 'response') - t.equal(res.statusCode, 413) + t.assert.equal(res.statusCode, 413) res.resume() await once(res, 'end') fs.unlinkSync(tmpFile) } catch (error) { - t.error(error, 'request') + t.assert.ifError(error) } }) @@ -380,7 +380,7 @@ test('should not throw fileSize limitation error when used alongside attachField t.plan(4) const fastify = Fastify() - t.teardown(fastify.close.bind(fastify)) + t.after(() => fastify.close()) fastify.register(multipart, { attachFieldsToBody: true @@ -398,13 +398,13 @@ test('should not throw fileSize limitation error when used alongside attachField } } }, async function (req, reply) { - t.ok(req.isMultipart()) + t.assert.ok(req.isMultipart()) - t.same(Object.keys(req.body), ['upload']) + t.assert.deepStrictEqual(Object.keys(req.body), ['upload']) const content = await req.body.upload.toBuffer() - t.equal(content.toString(), randomFileBuffer.toString()) + t.assert.strictEqual(content.toString(), randomFileBuffer.toString()) reply.status(200).send() }) @@ -431,12 +431,12 @@ test('should not throw fileSize limitation error when used alongside attachField try { const [res] = await once(req, 'response') - t.equal(res.statusCode, 200) + t.assert.equal(res.statusCode, 200) res.resume() await once(res, 'end') fs.unlinkSync(tmpFile) } catch (error) { - t.error(error, 'request') + t.assert.ifError(error) } }) From 819866694fea4923db1ad877c719d1726d68e8a5 Mon Sep 17 00:00:00 2001 From: id_minaev Date: Sun, 21 Sep 2025 20:19:11 +0400 Subject: [PATCH 4/6] test: add type test for routeOptions.config.multipartOptions --- types/index.test-d.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/types/index.test-d.ts b/types/index.test-d.ts index 7d33e365..b4fe66e5 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -179,6 +179,22 @@ const runServer = async () => { reply.send() }) + app.post('/upload/files', { + config: { + multipartOptions: {} + } + }, async function (req, reply) { + req.routeOptions.config.multipartOptions + expectType>(req.routeOptions.config.multipartOptions) + reply.send() + }) + + app.post('/upload/files', async function (req, reply) { + req.routeOptions.config.multipartOptions + expectError>(req.routeOptions.config?.multipartOptions) + reply.send() + }) + await app.ready() } From 2c398c4655669fdbeeeaf9354da3319eaa7563af Mon Sep 17 00:00:00 2001 From: id_minaev Date: Sun, 21 Sep 2025 20:36:33 +0400 Subject: [PATCH 5/6] docs: added docs entry for per route configuration when attachFieldsToBody is used --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 5eecb174..ae21552a 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,20 @@ fastify.post('/', async function (req, reply) { }) ``` +Or to a route options when `attachFieldsToBody` is used. +```js +fastify.post('/', { + config: { + multipartOptions: { + limits: { fileSize: 1000 } + } + } +}, async function (req, reply) { + const buffer = req.body.file.toBuffer(); + reply.send() +}) +``` + ## Handle multiple file streams ```js From b8f7038e2c95dd658a4098a5b44b59987b081881 Mon Sep 17 00:00:00 2001 From: id_minaev Date: Thu, 25 Sep 2025 22:50:39 +0400 Subject: [PATCH 6/6] fix: t.fail() => t.assert.fail() in multipart-fileLimit.test.js --- test/multipart-fileLimit.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/multipart-fileLimit.test.js b/test/multipart-fileLimit.test.js index 81a23f2a..6aed6a5e 100644 --- a/test/multipart-fileLimit.test.js +++ b/test/multipart-fileLimit.test.js @@ -339,7 +339,7 @@ test('should throw fileSize limitation error when used alongside attachFieldsToB } } }, async function (req, reply) { - t.fail('it should throw') + t.assert.fail('it should throw') reply.status(200).send() })