diff --git a/index.js b/index.js index ae8fa15..c051ea9 100644 --- a/index.js +++ b/index.js @@ -21,7 +21,13 @@ const formDataParser = async (instance, options) => { const bus = busboy({ headers: message.headers, limits, defParamCharset: "utf8" }); bus.on("file", (name, stream, info) => { results.push(storage.process(name, stream, info)); - body[name] = JSON.stringify(info); + if(!body[name]) { + body[name] = JSON.stringify(info); + } else if (body[name] && !Array.isArray(body[name])) { + body[name] = [body[name], JSON.stringify(info)]; + } else { + body[name].push(JSON.stringify(info)); + } }); bus.on("field", (name, value) => { body[name] = parseField(name, value); @@ -38,11 +44,19 @@ const formDataParser = async (instance, options) => { const body = request.body; const files = request.__files__; if (files?.length) { + const newBody = {}; for (const file of files) { const field = file.field; delete file.field; - body[field] = file; + if (!newBody[field]) { + newBody[field] = file; + } else if (newBody[field] && !Array.isArray(newBody[field])) { + newBody[field] = [newBody[field], file]; + } else { + newBody[field].push(file); + } } + Object.assign(body, newBody); } delete request.__files__; }); diff --git a/test/multifile.js b/test/multifile.js new file mode 100644 index 0000000..bc24cb0 --- /dev/null +++ b/test/multifile.js @@ -0,0 +1,51 @@ +"use strict"; + +const setup = require("./setup-multifile"); +const tap = require("tap"); +const { Readable } = require("stream"); +const { once } = require("events"); +const formData = require("form-data"); +const http = require("http"); +const path = require("path"); +const fs = require("fs"); + + +async function sendRequest(instance) { + const form = new formData(); + const req = http.request({ + protocol: "http:", + hostname: "localhost", + port: instance.server.address().port, + path: "/", + headers: form.getHeaders(), + method: "POST" + }); + const filePath = path.join(__dirname, "chequer.png"); + form.append("file", fs.createReadStream(filePath)); + form.append("files", fs.createReadStream(filePath)); + form.append("files", fs.createReadStream(filePath)); + form.append("files", fs.createReadStream(filePath)); + return form.pipe(req); +}; + +tap.test("should allow multiple files in one field", async t => { + const instance = require("fastify").fastify(); + t.teardown(async () => { + await instance.close(); + }); + try { + instance.addHook("onResponse", async (request, reply) => { + const requestBody = request.body; + t.isArray(requestBody.files); + t.ok(requestBody.files[0].stream instanceof Readable); + t.ok(requestBody.files[1].stream instanceof Readable); + t.equal(reply.statusCode, 200); + }); + await setup(instance, undefined, false); + const req = await sendRequest(instance); + const [res] = await once(req, "response"); + res.resume(); + } catch (err) { + console.log(err); + } +}); \ No newline at end of file diff --git a/test/no-schema.js b/test/no-schema.js index 8839add..e35122d 100644 --- a/test/no-schema.js +++ b/test/no-schema.js @@ -25,4 +25,4 @@ tap.test("should parse fields as strings when there is no schema", async t => { } catch (err) { console.log(err); } -}); \ No newline at end of file +}); diff --git a/test/setup-multifile.js b/test/setup-multifile.js new file mode 100644 index 0000000..fb58cfa --- /dev/null +++ b/test/setup-multifile.js @@ -0,0 +1,36 @@ +"use strict"; + +const formDataParser = require("../index"); + +const requestSchema = { + consumes: ["multipart/form-data"], + body: { + type: "object", + properties: { + file: { + type: "string", + format: "binary" + }, + files: { + type: "array", + items: { + type: "string", + format: "binary" + } + }, + } + } +}; +module.exports = async function (instance, options = undefined, includeSchema = true) { + instance.register(formDataParser, options); + instance.post( + "/", + { + schema: includeSchema && requestSchema + }, + async (request, reply) => { + reply.status(200).send(); + } + ); + await instance.listen({ port: 0, host: "::" }); +}; \ No newline at end of file