Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: part.type for easy type narrowing #422

Merged
merged 5 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,10 @@ fastify.post('/', async function (req, reply) {
fastify.post('/upload/raw/any', async function (req, reply) {
const parts = req.parts()
for await (const part of parts) {
if (part.file) {
if (part.type === 'file') {
await pump(part.file, fs.createWriteStream(part.filename))
} else {
// part.type === 'field
console.log(part)
}
}
Expand Down Expand Up @@ -177,6 +178,7 @@ This will store all files in the operating system default directory for temporar
fastify.post('/upload/files', async function (req, reply) {
// stores files to tmp dir and return files
const files = await req.saveRequestFiles()
files[0].type // "file"
files[0].filepath
files[0].fieldname
files[0].filename
Expand Down
3 changes: 2 additions & 1 deletion examples/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ fastify.post('/upload/stream/files', async function (req, reply) {
fastify.post('/upload/raw/any', async function (req, reply) {
const parts = req.parts()
for await (const part of parts) {
if (part.file) {
if (part.type === 'file') {
await pump(part.file, fs.createWriteStream(part.filename))
} else {
// part.type === 'field'
console.log(part)
}
}
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ declare namespace fastifyMultipart {
export type Multipart = MultipartFile | MultipartValue;

export interface MultipartFile {
type: 'file';
toBuffer: () => Promise<Buffer>;
file: BusboyFileStream;
fieldname: string;
Expand All @@ -92,6 +93,7 @@ declare namespace fastifyMultipart {
}

export interface MultipartValue<T = unknown> {
type: 'field';
value: T;
fieldname: string;
mimetype: string;
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@ function fastifyMultipart (fastify, options, done) {
}

const value = {
type: 'field',
fieldname: name,
mimetype: contentType,
encoding,
Expand Down Expand Up @@ -444,6 +445,7 @@ function fastifyMultipart (fastify, options, done) {
: defaultThrowFileSizeLimit

const value = {
type: 'file',
fieldname: name,
filename,
encoding,
Expand Down
3 changes: 2 additions & 1 deletion test/big.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const sendToWormhole = require('stream-wormhole')

// skipping on Github Actions because it takes too long
test('should upload a big file in constant memory', { skip: process.env.CI }, function (t) {
t.plan(9)
t.plan(10)

const fastify = Fastify()
const hashInput = crypto.createHash('sha256')
Expand All @@ -32,6 +32,7 @@ test('should upload a big file in constant memory', { skip: process.env.CI }, fu

for await (const part of req.parts()) {
if (part.file) {
t.equal(part.type, 'file')
t.equal(part.fieldname, 'upload')
t.equal(part.filename, 'random-data')
t.equal(part.encoding, '7bit')
Expand Down
5 changes: 4 additions & 1 deletion test/fix-313.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const { once } = EventEmitter
const filePath = path.join(__dirname, '../README.md')

test('should store file on disk, remove on response when attach fields to body is true', async function (t) {
t.plan(22)
t.plan(25)

const fastify = Fastify()
t.teardown(fastify.close.bind(fastify))
Expand All @@ -29,18 +29,21 @@ test('should store file on disk, remove on response when attach fields to body i
const files = await req.saveRequestFiles()

t.ok(files[0].filepath)
t.equal(files[0].type, 'file')
t.equal(files[0].fieldname, 'upload')
t.equal(files[0].filename, 'README.md')
t.equal(files[0].encoding, '7bit')
t.equal(files[0].mimetype, 'text/markdown')
t.ok(files[0].fields.upload)
t.ok(files[1].filepath)
t.equal(files[1].type, 'file')
t.equal(files[1].fieldname, 'upload')
t.equal(files[1].filename, 'README.md')
t.equal(files[1].encoding, '7bit')
t.equal(files[1].mimetype, 'text/markdown')
t.ok(files[1].fields.upload)
t.ok(files[2].filepath)
t.equal(files[2].type, 'file')
t.equal(files[2].fieldname, 'other')
t.equal(files[2].filename, 'README.md')
t.equal(files[2].encoding, '7bit')
Expand Down
12 changes: 7 additions & 5 deletions test/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import fastifyMultipart, {MultipartValue, MultipartFields, MultipartFile } from
import * as util from 'util'
import { pipeline } from 'stream'
import * as fs from 'fs'
import { expectError, expectType } from 'tsd'
import { expectError, expectType, expectAssignable } from 'tsd'
import { FastifyErrorConstructor } from "@fastify/error"
import { BusboyConfig, BusboyFileStream } from "@fastify/busboy";

Expand Down Expand Up @@ -55,7 +55,8 @@ const runServer = async () => {
app.post('/', async (req, reply) => {
const data = await req.file()
if (data == null) throw new Error('missing file')


expectAssignable<string>(data.type)
expectType<BusboyFileStream>(data.file)
expectType<boolean>(data.file.truncated)
expectType<MultipartFields>(data.fields)
Expand All @@ -69,7 +70,7 @@ const runServer = async () => {
// field missing from the request
} else if (Array.isArray(field)) {
// multiple fields with the same name
} else if ('file' in field) {
} else if (field.type === 'file') {
// field containing a file
field.file.resume()
} else {
Expand All @@ -88,7 +89,7 @@ const runServer = async () => {
expectType<string>(req.body.foo.value);

expectType<BusboyFileStream>(req.body.file.file)
expectError(req.body.file.value);
expectAssignable<string>(req.body.file.type);
reply.send();
})

Expand Down Expand Up @@ -123,7 +124,7 @@ const runServer = async () => {
app.post('/upload/raw/any', async function (req, reply) {
const parts = req.parts()
for await (const part of parts) {
if ('file' in part) {
if (part.type === 'file') {
await pump(part.file, fs.createWriteStream(part.filename))
} else {
console.log(part.value)
Expand All @@ -145,6 +146,7 @@ const runServer = async () => {
app.post('/upload/files', async function (req, reply) {
// stores files to tmp dir and return files
const files = await req.saveRequestFiles()
files[0].type // "file"
files[0].filepath
files[0].fieldname
files[0].filename
Expand Down
3 changes: 2 additions & 1 deletion test/multipart-body-schema.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const fs = require('fs')
const filePath = path.join(__dirname, '../README.md')

test('should be able to use JSON schema to validate request', function (t) {
t.plan(6)
t.plan(7)

const fastify = Fastify()
t.teardown(fastify.close.bind(fastify))
Expand Down Expand Up @@ -39,6 +39,7 @@ test('should be able to use JSON schema to validate request', function (t) {
const content = await req.body.upload.toBuffer()

t.equal(content.toString(), original)
t.equal(req.body.hello.type, 'field')
t.equal(req.body.hello.value, 'world')

reply.code(200).send()
Expand Down
16 changes: 11 additions & 5 deletions test/multipart.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const sendToWormhole = require('stream-wormhole')
const filePath = path.join(__dirname, '../README.md')

test('should parse forms', function (t) {
t.plan(8)
t.plan(9)

const fastify = Fastify()
t.teardown(fastify.close.bind(fastify))
Expand All @@ -27,6 +27,7 @@ test('should parse forms', function (t) {
fastify.post('/', async function (req, reply) {
for await (const part of req.parts()) {
if (part.file) {
t.equal(part.type, 'file')
t.equal(part.fieldname, 'upload')
t.equal(part.filename, 'README.md')
t.equal(part.encoding, '7bit')
Expand Down Expand Up @@ -76,7 +77,7 @@ test('should parse forms', function (t) {
})

test('should respond when all files are processed', function (t) {
t.plan(4)
t.plan(6)

const fastify = Fastify()
t.teardown(fastify.close.bind(fastify))
Expand All @@ -87,6 +88,7 @@ test('should respond when all files are processed', function (t) {
const parts = req.files()
for await (const part of parts) {
t.ok(part.file)
t.equal(part.type, 'file')
await sendToWormhole(part.file)
}
reply.code(200).send()
Expand Down Expand Up @@ -327,6 +329,7 @@ test('should be able to configure limits globally with plugin register options',
const parts = req.files()
for await (const part of parts) {
t.ok(part.file)
t.equal(part.type, 'file')
await sendToWormhole(part.file)
}
reply.code(200).send()
Expand Down Expand Up @@ -469,7 +472,7 @@ test('should throw error due to partsLimit (The max number of parts (fields + fi
})

test('should throw error due to file size limit exceed (Default: true)', function (t) {
t.plan(4)
t.plan(6)

const fastify = Fastify()
t.teardown(fastify.close.bind(fastify))
Expand All @@ -481,6 +484,7 @@ test('should throw error due to file size limit exceed (Default: true)', functio
const parts = req.files()
for await (const part of parts) {
t.ok(part.file)
t.equal(part.type, 'file')
await sendToWormhole(part.file)
}
reply.code(200).send()
Expand Down Expand Up @@ -516,7 +520,7 @@ test('should throw error due to file size limit exceed (Default: true)', functio
})

test('should not throw error due to file size limit exceed - files setting (Default: true)', function (t) {
t.plan(3)
t.plan(5)

const fastify = Fastify()
t.teardown(fastify.close.bind(fastify))
Expand All @@ -527,6 +531,7 @@ test('should not throw error due to file size limit exceed - files setting (Defa
const parts = req.files({ limits: { fileSize: 1 } })
for await (const part of parts) {
t.ok(part.file)
t.equal(part.type, 'file')
await sendToWormhole(part.file)
}
reply.code(200).send()
Expand Down Expand Up @@ -557,7 +562,7 @@ test('should not throw error due to file size limit exceed - files setting (Defa
})

test('should not miss fields if part handler takes much time than formdata parsing', async function (t) {
t.plan(11)
t.plan(12)

const original = fs.readFileSync(filePath, 'utf8')
const immediate = util.promisify(setImmediate)
Expand All @@ -576,6 +581,7 @@ test('should not miss fields if part handler takes much time than formdata parsi

for await (const part of req.parts()) {
if (part.file) {
t.equal(part.type, 'file')
t.equal(part.fieldname, 'upload')
t.equal(part.filename, 'README.md')
t.equal(part.encoding, '7bit')
Expand Down