Skip to content

Commit 22c7c8d

Browse files
committed
zlib: improve zlib errors
- Use assert to check mode in the Zlib constructor since it should only be passed by us. - Introduce checkRangesOrGetDefault() and checkFiniteNumber() to simplify type and range checking for numeric arguments - Instead of `ERR_INVALID_OPT_VALUE`, throw `ERR_OUT_OF_RANGE` and `ERR_INVALID_ARG_TYPE` with descriptions of the expected ranges or types to make the errors more user-friendly. - Add message tests for the changed errors
1 parent 472cde6 commit 22c7c8d

File tree

4 files changed

+321
-130
lines changed

4 files changed

+321
-130
lines changed

lib/zlib.js

Lines changed: 82 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,56 @@ function flushCallback(level, strategy, callback) {
156156
}
157157
}
158158

159+
// 1. Returns true for finite numbers
160+
// 2. Returns false for undefined and NaN
161+
// 3. Throws ERR_INVALID_ARG_TYPE for non-numbers
162+
// 4. Throws ERR_OUT_OF_RANGE for infinite numbers
163+
function checkFiniteNumber(number, name) {
164+
if (Number.isFinite(number)) {
165+
return true; // is a valid number
166+
} else {
167+
// undefined or NaN
168+
if (number === undefined || Number.isNaN(number)) {
169+
return false;
170+
}
171+
172+
// Other non-numbers
173+
if (typeof number !== 'number') {
174+
const err = new errors.TypeError('ERR_INVALID_ARG_TYPE', name,
175+
'number', number);
176+
Error.captureStackTrace(err, checkFiniteNumber);
177+
throw err;
178+
}
179+
180+
// Infinite numbers
181+
const err = new errors.RangeError('ERR_OUT_OF_RANGE', name,
182+
'a finite number', number);
183+
Error.captureStackTrace(err, checkFiniteNumber);
184+
throw err;
185+
}
186+
}
187+
188+
function checkRanges(number, name, lower, upper) {
189+
if (number < lower || number > upper) {
190+
const err = new errors.RangeError(
191+
'ERR_OUT_OF_RANGE', name, `>= ${lower} and <= ${upper}`, number);
192+
Error.captureStackTrace(err, checkRanges);
193+
throw err;
194+
}
195+
}
196+
197+
// 1. Returns number for finite numbers >= lower and <= upper
198+
// 2. Returns def for undefined and NaN
199+
// 3. Throws ERR_INVALID_ARG_TYPE for non-numbers
200+
// 4. Throws ERR_OUT_OF_RANGE for infinite numbers or numbers > upper or < lower
201+
function checkRangesOrGetDefault(number, name, lower, upper, def) {
202+
if (checkFiniteNumber(number, name, lower, upper)) {
203+
checkRanges(number, name, lower, upper);
204+
return number;
205+
}
206+
return def;
207+
}
208+
159209
// the Zlib class they all inherit from
160210
// This thing manages the queue of requests, and returns
161211
// true or false if there is anything in the queue when
@@ -170,95 +220,53 @@ function Zlib(opts, mode) {
170220
var strategy = Z_DEFAULT_STRATEGY;
171221
var dictionary;
172222

173-
if (typeof mode !== 'number')
174-
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'mode', 'number');
175-
if (mode < DEFLATE || mode > UNZIP)
176-
throw new errors.RangeError('ERR_OUT_OF_RANGE', 'mode');
223+
// The Zlib class is not exported to user land, the mode should only be
224+
// passed in by us.
225+
assert(typeof mode === 'number');
226+
assert(mode >= DEFLATE && mode <= UNZIP);
177227

178228
if (opts) {
179229
chunkSize = opts.chunkSize;
180-
if (chunkSize !== undefined && !Number.isNaN(chunkSize)) {
181-
if (chunkSize < Z_MIN_CHUNK || !Number.isFinite(chunkSize))
182-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
183-
'chunkSize',
184-
chunkSize);
185-
} else {
230+
if (!checkFiniteNumber(chunkSize, 'options.chunkSize')) {
186231
chunkSize = Z_DEFAULT_CHUNK;
232+
} else if (chunkSize < Z_MIN_CHUNK) {
233+
throw new errors.RangeError('ERR_OUT_OF_RANGE', 'options.chunkSize',
234+
`>= ${Z_MIN_CHUNK}`, chunkSize);
187235
}
188236

189-
flush = opts.flush;
190-
if (flush !== undefined && !Number.isNaN(flush)) {
191-
if (flush < Z_NO_FLUSH || flush > Z_BLOCK || !Number.isFinite(flush))
192-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE', 'flush', flush);
193-
} else {
194-
flush = Z_NO_FLUSH;
195-
}
237+
flush = checkRangesOrGetDefault(
238+
opts.flush, 'options.flush',
239+
Z_NO_FLUSH, Z_BLOCK, Z_NO_FLUSH);
196240

197-
finishFlush = opts.finishFlush;
198-
if (finishFlush !== undefined && !Number.isNaN(finishFlush)) {
199-
if (finishFlush < Z_NO_FLUSH || finishFlush > Z_BLOCK ||
200-
!Number.isFinite(finishFlush)) {
201-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
202-
'finishFlush',
203-
finishFlush);
204-
}
205-
} else {
206-
finishFlush = Z_FINISH;
207-
}
241+
finishFlush = checkRangesOrGetDefault(
242+
opts.finishFlush, 'options.finishFlush',
243+
Z_NO_FLUSH, Z_BLOCK, Z_FINISH);
208244

209-
windowBits = opts.windowBits;
210-
if (windowBits !== undefined && !Number.isNaN(windowBits)) {
211-
if (windowBits < Z_MIN_WINDOWBITS || windowBits > Z_MAX_WINDOWBITS ||
212-
!Number.isFinite(windowBits)) {
213-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
214-
'windowBits',
215-
windowBits);
216-
}
217-
} else {
218-
windowBits = Z_DEFAULT_WINDOWBITS;
219-
}
245+
windowBits = checkRangesOrGetDefault(
246+
opts.windowBits, 'options.windowBits',
247+
Z_MIN_WINDOWBITS, Z_MAX_WINDOWBITS, Z_DEFAULT_WINDOWBITS);
220248

221-
level = opts.level;
222-
if (level !== undefined && !Number.isNaN(level)) {
223-
if (level < Z_MIN_LEVEL || level > Z_MAX_LEVEL ||
224-
!Number.isFinite(level)) {
225-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
226-
'level', level);
227-
}
228-
} else {
229-
level = Z_DEFAULT_COMPRESSION;
230-
}
249+
level = checkRangesOrGetDefault(
250+
opts.level, 'options.level',
251+
Z_MIN_LEVEL, Z_MAX_LEVEL, Z_DEFAULT_COMPRESSION);
231252

232-
memLevel = opts.memLevel;
233-
if (memLevel !== undefined && !Number.isNaN(memLevel)) {
234-
if (memLevel < Z_MIN_MEMLEVEL || memLevel > Z_MAX_MEMLEVEL ||
235-
!Number.isFinite(memLevel)) {
236-
throw new errors.RangeError('ERR_INVALID_OPT_VALUE',
237-
'memLevel', memLevel);
238-
}
239-
} else {
240-
memLevel = Z_DEFAULT_MEMLEVEL;
241-
}
253+
memLevel = checkRangesOrGetDefault(
254+
opts.memLevel, 'options.memLevel',
255+
Z_MIN_MEMLEVEL, Z_MAX_MEMLEVEL, Z_DEFAULT_MEMLEVEL);
242256

243-
strategy = opts.strategy;
244-
if (strategy !== undefined && !Number.isNaN(strategy)) {
245-
if (strategy < Z_DEFAULT_STRATEGY || strategy > Z_FIXED ||
246-
!Number.isFinite(strategy)) {
247-
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
248-
'strategy', strategy);
249-
}
250-
} else {
251-
strategy = Z_DEFAULT_STRATEGY;
252-
}
257+
strategy = checkRangesOrGetDefault(
258+
opts.strategy, 'options.strategy',
259+
Z_DEFAULT_STRATEGY, Z_FIXED, Z_DEFAULT_STRATEGY);
253260

254261
dictionary = opts.dictionary;
255262
if (dictionary !== undefined && !isArrayBufferView(dictionary)) {
256263
if (isAnyArrayBuffer(dictionary)) {
257264
dictionary = Buffer.from(dictionary);
258265
} else {
259-
throw new errors.TypeError('ERR_INVALID_OPT_VALUE',
260-
'dictionary',
261-
dictionary);
266+
throw new errors.TypeError(
267+
'ERR_INVALID_ARG_TYPE', 'options.dictionary',
268+
['Buffer', 'TypedArray', 'DataView', 'ArrayBuffer'],
269+
dictionary);
262270
}
263271
}
264272

@@ -310,14 +318,8 @@ Object.defineProperty(Zlib.prototype, '_closed', {
310318
});
311319

312320
Zlib.prototype.params = function params(level, strategy, callback) {
313-
if (level < Z_MIN_LEVEL || level > Z_MAX_LEVEL)
314-
throw new errors.RangeError('ERR_INVALID_ARG_VALUE', 'level', level);
315-
316-
if (strategy !== undefined &&
317-
(strategy < Z_DEFAULT_STRATEGY || strategy > Z_FIXED ||
318-
!Number.isFinite(strategy))) {
319-
throw new errors.TypeError('ERR_INVALID_ARG_VALUE', 'strategy', strategy);
320-
}
321+
checkRangesOrGetDefault(level, 'level', Z_MIN_LEVEL, Z_MAX_LEVEL);
322+
checkRangesOrGetDefault(strategy, 'strategy', Z_DEFAULT_STRATEGY, Z_FIXED);
321323

322324
if (this._level !== level || this._strategy !== strategy) {
323325
this.flush(Z_SYNC_FLUSH,

0 commit comments

Comments
 (0)