Skip to content

Commit 8a74c8a

Browse files
authored
* feat: add code and httpCode to most errors (#686)
1 parent 08b09c5 commit 8a74c8a

14 files changed

+138
-29
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,11 @@ const server = http.createServer((req, res) => {
113113
const form = formidable({ multiples: true });
114114

115115
form.parse(req, (err, fields, files) => {
116+
if (err) {
117+
res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' });
118+
res.end(String(err));
119+
return;
120+
}
116121
res.writeHead(200, { 'Content-Type': 'application/json' });
117122
res.end(JSON.stringify({ fields, files }, null, 2));
118123
});
@@ -655,6 +660,8 @@ Emitted when there is an error processing the incoming form. A request that
655660
experiences an error is automatically paused, you will have to manually call
656661
`request.resume()` if you want the request to continue firing `'data'` events.
657662

663+
May have `error.httpCode` and `error.code` attached.
664+
658665
```js
659666
form.on('error', (err) => {});
660667
```

examples/with-http.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const server = http.createServer((req, res) => {
1818
form.parse(req, (err, fields, files) => {
1919
if (err) {
2020
console.error(err);
21-
res.writeHead(400, { 'Content-Type': 'text/plain' });
22-
res.end('Bad Request');
21+
res.writeHead(err.httpCode || 400, { 'Content-Type': 'text/plain' });
22+
res.end(String(err));
2323
return;
2424
}
2525
res.writeHead(200, { 'Content-Type': 'application/json' });

src/Formidable.js

+53-14
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ const PersistentFile = require('./PersistentFile');
3333
const VolatileFile = require('./VolatileFile');
3434
const DummyParser = require('./parsers/Dummy');
3535
const MultipartParser = require('./parsers/Multipart');
36+
const errors = require('./FormidableError.js');
37+
38+
const { FormidableError } = errors;
3639

3740
function hasOwnProp(obj, key) {
3841
return Object.prototype.hasOwnProperty.call(obj, key);
@@ -63,7 +66,6 @@ class IncomingForm extends EventEmitter {
6366

6467
this._setUpRename();
6568

66-
6769
this._flushing = 0;
6870
this._fieldsSize = 0;
6971
this._fileSize = 0;
@@ -75,8 +77,9 @@ class IncomingForm extends EventEmitter {
7577
.filter(Boolean);
7678

7779
if (this.options.enabledPlugins.length === 0) {
78-
throw new Error(
80+
throw new FormidableError(
7981
'expect at least 1 enabled builtin plugin, see options.enabledPlugins',
82+
errors.missingPlugin,
8083
);
8184
}
8285

@@ -91,7 +94,10 @@ class IncomingForm extends EventEmitter {
9194

9295
use(plugin) {
9396
if (typeof plugin !== 'function') {
94-
throw new Error('.use: expect `plugin` to be a function');
97+
throw new FormidableError(
98+
'.use: expect `plugin` to be a function',
99+
errors.pluginFunction,
100+
);
95101
}
96102
this._plugins.push(plugin.bind(this));
97103
return this;
@@ -183,7 +189,7 @@ class IncomingForm extends EventEmitter {
183189
})
184190
.on('aborted', () => {
185191
this.emit('aborted');
186-
this._error(new Error('Request aborted'));
192+
this._error(new FormidableError('Request aborted', errors.aborted));
187193
})
188194
.on('data', (buffer) => {
189195
try {
@@ -211,7 +217,13 @@ class IncomingForm extends EventEmitter {
211217
this._parseContentType();
212218

213219
if (!this._parser) {
214-
this._error(new Error('no parser found'));
220+
this._error(
221+
new FormidableError(
222+
'no parser found',
223+
errors.noParser,
224+
415, // Unsupported Media Type
225+
),
226+
);
215227
return;
216228
}
217229

@@ -225,7 +237,9 @@ class IncomingForm extends EventEmitter {
225237
return null;
226238
}
227239
if (!this._parser) {
228-
this._error(new Error('uninitialized parser'));
240+
this._error(
241+
new FormidableError('uninitialized parser', errors.uninitializedParser),
242+
);
229243
return null;
230244
}
231245

@@ -254,7 +268,12 @@ class IncomingForm extends EventEmitter {
254268

255269
_handlePart(part) {
256270
if (part.filename && typeof part.filename !== 'string') {
257-
this._error(new Error(`the part.filename should be string when exists`));
271+
this._error(
272+
new FormidableError(
273+
`the part.filename should be string when it exists`,
274+
errors.filenameNotString,
275+
),
276+
);
258277
return;
259278
}
260279

@@ -278,8 +297,10 @@ class IncomingForm extends EventEmitter {
278297
this._fieldsSize += buffer.length;
279298
if (this._fieldsSize > this.options.maxFieldsSize) {
280299
this._error(
281-
new Error(
300+
new FormidableError(
282301
`options.maxFieldsSize (${this.options.maxFieldsSize} bytes) exceeded, received ${this._fieldsSize} bytes of field data`,
302+
errors.maxFieldsSizeExceeded,
303+
413, // Payload Too Large
283304
),
284305
);
285306
return;
@@ -312,16 +333,20 @@ class IncomingForm extends EventEmitter {
312333
this._fileSize += buffer.length;
313334
if (this._fileSize < this.options.minFileSize) {
314335
this._error(
315-
new Error(
336+
new FormidableError(
316337
`options.minFileSize (${this.options.minFileSize} bytes) inferior, received ${this._fileSize} bytes of file data`,
338+
errors.smallerThanMinFileSize,
339+
400,
317340
),
318341
);
319342
return;
320343
}
321344
if (this._fileSize > this.options.maxFileSize) {
322345
this._error(
323-
new Error(
346+
new FormidableError(
324347
`options.maxFileSize (${this.options.maxFileSize} bytes) exceeded, received ${this._fileSize} bytes of file data`,
348+
errors.biggerThanMaxFileSize,
349+
413,
325350
),
326351
);
327352
return;
@@ -338,8 +363,10 @@ class IncomingForm extends EventEmitter {
338363
part.on('end', () => {
339364
if (!this.options.allowEmptyFiles && this._fileSize === 0) {
340365
this._error(
341-
new Error(
366+
new FormidableError(
342367
`options.allowEmptyFiles is false, file size should be greather than 0`,
368+
errors.noEmptyFiles,
369+
400,
343370
),
344371
);
345372
return;
@@ -361,7 +388,13 @@ class IncomingForm extends EventEmitter {
361388
}
362389

363390
if (!this.headers['content-type']) {
364-
this._error(new Error('bad content-type header, no content-type'));
391+
this._error(
392+
new FormidableError(
393+
'bad content-type header, no content-type',
394+
errors.missingContentType,
395+
400,
396+
),
397+
);
365398
return;
366399
}
367400

@@ -379,8 +412,10 @@ class IncomingForm extends EventEmitter {
379412
} catch (err) {
380413
// directly throw from the `form.parse` method;
381414
// there is no other better way, except a handle through options
382-
const error = new Error(
415+
const error = new FormidableError(
383416
`plugin on index ${idx} failed with: ${err.message}`,
417+
errors.pluginFailed,
418+
500,
384419
);
385420
error.idx = idx;
386421
throw error;
@@ -524,7 +559,11 @@ class IncomingForm extends EventEmitter {
524559
fieldsCount += 1;
525560
if (fieldsCount > this.options.maxFields) {
526561
this._error(
527-
new Error(`options.maxFields (${this.options.maxFields}) exceeded`),
562+
new FormidableError(
563+
`options.maxFields (${this.options.maxFields}) exceeded`,
564+
errors.maxFieldsExceeded,
565+
413,
566+
),
528567
);
529568
}
530569
});

src/FormidableError.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/* eslint-disable no-plusplus */
2+
3+
let errorCodes = 0;
4+
const missingPlugin = errorCodes++;
5+
const pluginFunction = errorCodes++;
6+
const aborted = errorCodes++;
7+
const noParser = errorCodes++;
8+
const uninitializedParser = errorCodes++;
9+
const filenameNotString = errorCodes++;
10+
const maxFieldsSizeExceeded = errorCodes++;
11+
const maxFieldsExceeded = errorCodes++;
12+
const smallerThanMinFileSize = errorCodes++;
13+
const biggerThanMaxFileSize = errorCodes++;
14+
const noEmptyFiles = errorCodes++;
15+
const missingContentType = errorCodes++;
16+
const malformedMultipart = errorCodes++;
17+
const missingMultipartBoundary = errorCodes++;
18+
const unknownTransferEncoding = errorCodes++;
19+
20+
const FormidableError = class extends Error {
21+
constructor(message, internalCode, httpCode = 500) {
22+
super(message);
23+
this.code = internalCode;
24+
this.httpCode = httpCode;
25+
}
26+
};
27+
28+
module.exports = {
29+
missingPlugin,
30+
pluginFunction,
31+
aborted,
32+
noParser,
33+
uninitializedParser,
34+
filenameNotString,
35+
maxFieldsSizeExceeded,
36+
maxFieldsExceeded,
37+
smallerThanMinFileSize,
38+
biggerThanMaxFileSize,
39+
noEmptyFiles,
40+
missingContentType,
41+
malformedMultipart,
42+
missingMultipartBoundary,
43+
unknownTransferEncoding,
44+
45+
FormidableError,
46+
};

src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const PersistentFile = require('./PersistentFile');
44
const VolatileFile = require('./VolatileFile');
55
const Formidable = require('./Formidable');
6+
const FormidableError = require('./FormidableError');
67

78
const plugins = require('./plugins/index');
89
const parsers = require('./parsers/index');
@@ -12,6 +13,7 @@ const parsers = require('./parsers/index');
1213
const formidable = (...args) => new Formidable(...args);
1314

1415
module.exports = Object.assign(formidable, {
16+
errors: FormidableError,
1517
File: PersistentFile,
1618
PersistentFile,
1719
VolatileFile,

src/parsers/Multipart.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
'use strict';
77

88
const { Transform } = require('stream');
9+
const errors = require('../FormidableError.js');
10+
11+
const { FormidableError } = errors;
912

1013
let s = 0;
1114
const STATE = {
@@ -69,8 +72,10 @@ class MultipartParser extends Transform {
6972
done();
7073
} else if (this.state !== STATE.END) {
7174
done(
72-
new Error(
75+
new FormidableError(
7376
`MultipartParser.end(): stream ended unexpectedly: ${this.explain()}`,
77+
errors.malformedMultipart,
78+
400,
7479
),
7580
);
7681
}

src/plugins/multipart.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
const { Stream } = require('stream');
66
const MultipartParser = require('../parsers/Multipart');
7+
const errors = require('../FormidableError.js');
8+
9+
const { FormidableError } = errors;
710

811
// the `options` is also available through the `options` / `formidable.options`
912
module.exports = function plugin(formidable, options) {
@@ -24,7 +27,11 @@ module.exports = function plugin(formidable, options) {
2427
const initMultipart = createInitMultipart(m[1] || m[2]);
2528
initMultipart.call(self, self, options); // lgtm [js/superfluous-trailing-arguments]
2629
} else {
27-
const err = new Error('bad content-type header, no multipart boundary');
30+
const err = new FormidableError(
31+
'bad content-type header, no multipart boundary',
32+
errors.missingMultipartBoundary,
33+
400,
34+
);
2835
self._error(err);
2936
}
3037
}
@@ -145,7 +152,13 @@ function createInitMultipart(boundary) {
145152
break;
146153
}
147154
default:
148-
return this._error(new Error('unknown transfer-encoding'));
155+
return this._error(
156+
new FormidableError(
157+
'unknown transfer-encoding',
158+
errors.unknownTransferEncoding,
159+
501,
160+
),
161+
);
149162
}
150163

151164
this.onPart(part);

test/integration/file-write-stream-handler-option.test.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ const createDirs = (dirs) => {
3333
});
3434
};
3535

36-
3736
test('file write stream handler', (done) => {
3837
const server = http.createServer((req, res) => {
3938
createDirs([DEFAULT_UPLOAD_DIR, CUSTOM_UPLOAD_DIR]);
4039
const form = formidable({
4140
uploadDir: DEFAULT_UPLOAD_DIR,
42-
fileWriteStreamHandler: () => fs.createWriteStream(CUSTOM_UPLOAD_FILE_PATH),
41+
fileWriteStreamHandler: () =>
42+
fs.createWriteStream(CUSTOM_UPLOAD_FILE_PATH),
4343
});
4444

4545
form.parse(req, (err, fields, files) => {
@@ -63,8 +63,6 @@ test('file write stream handler', (done) => {
6363
});
6464

6565
server.listen(PORT, (err) => {
66-
const choosenPort = server.address().port;
67-
6866
assert(!err, 'should not have error, but be falsey');
6967

7068
const request = http.request({

test/integration/fixtures.test.js

-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ const FIXTURES_PATH = path.join(CWD, 'test', 'fixture', 'js');
1717
const FIXTURES_HTTP = path.join(CWD, 'test', 'fixture', 'http');
1818
const UPLOAD_DIR = path.join(CWD, 'test', 'tmp');
1919

20-
2120
test('fixtures', (done) => {
2221
const server = http.createServer();
2322
server.listen(PORT, findFixtures);

test/integration/octet-stream.test.js

-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const testFilePath = path.join(
1515
'binaryfile.tar.gz',
1616
);
1717

18-
1918
test('octet stream', (done) => {
2019
const server = http.createServer((req, res) => {
2120
const form = formidable();

test/integration/store-files-option.test.js

-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const testFilePath = path.join(
2121
'binaryfile.tar.gz',
2222
);
2323

24-
2524
test('store files option', (done) => {
2625
const server = http.createServer((req, res) => {
2726
if (!fs.existsSync(DEFAULT_UPLOAD_DIR)) {

test/standalone/connection-aborted.test.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ test('connection aborted', (done) => {
2727
abortedReceived,
2828
'from .parse() callback: Error event should follow aborted',
2929
);
30-
30+
3131
server.close();
3232
done();
3333
});
@@ -45,5 +45,4 @@ test('connection aborted', (done) => {
4545
);
4646
client.end();
4747
});
48-
49-
})
48+
});

0 commit comments

Comments
 (0)