diff --git a/doc/api/errors.md b/doc/api/errors.md index 63d2e1c20d5346..c3c5e497d3afb4 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1303,6 +1303,11 @@ An invalid HTTP token was supplied. An IP address is not valid. + +### `ERR_INVALID_OPT_TYPE` + +An option of the wrong type was passed in an options object. + ### `ERR_INVALID_OPT_VALUE` diff --git a/lib/internal/errors.js b/lib/internal/errors.js index 206724eacb6f52..14189cd26c6039 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -686,6 +686,114 @@ const fatalExceptionStackEnhancers = { } }; +const invalidValueTypeError = (name, expected, actual, valueType) => { + assert(typeof name === 'string', "'name' must be a string"); + if (!ArrayIsArray(expected)) { + expected = [expected]; + } + + let msg = 'The '; + if (valueType !== undefined) { + msg += `"${name}" ${valueType} `; + } else if (name.endsWith(' argument')) { + // For cases like 'first argument' + msg += `${name} `; + } else { + const type = name.includes('.') ? 'property' : 'argument'; + msg += `"${name}" ${type} `; + } + msg += 'must be '; + + const types = []; + const instances = []; + const other = []; + + for (const value of expected) { + assert(typeof value === 'string', + 'All expected entries have to be of type string'); + if (kTypes.includes(value)) { + types.push(value.toLowerCase()); + } else if (classRegExp.test(value)) { + instances.push(value); + } else { + assert(value !== 'object', + 'The value "object" should be written as "Object"'); + other.push(value); + } + } + + // Special handle `object` in case other instances are allowed to outline + // the differences between each other. + if (instances.length > 0) { + const pos = types.indexOf('object'); + if (pos !== -1) { + types.splice(pos, 1); + instances.push('Object'); + } + } + + if (types.length > 0) { + if (types.length > 2) { + const last = types.pop(); + msg += `one of type ${types.join(', ')}, or ${last}`; + } else if (types.length === 2) { + msg += `one of type ${types[0]} or ${types[1]}`; + } else { + msg += `of type ${types[0]}`; + } + if (instances.length > 0 || other.length > 0) + msg += ' or '; + } + + if (instances.length > 0) { + if (instances.length > 2) { + const last = instances.pop(); + msg += `an instance of ${instances.join(', ')}, or ${last}`; + } else { + msg += `an instance of ${instances[0]}`; + if (instances.length === 2) { + msg += ` or ${instances[1]}`; + } + } + if (other.length > 0) + msg += ' or '; + } + + if (other.length > 0) { + if (other.length > 2) { + const last = other.pop(); + msg += `one of ${other.join(', ')}, or ${last}`; + } else if (other.length === 2) { + msg += `one of ${other[0]} or ${other[1]}`; + } else { + if (other[0].toLowerCase() !== other[0]) + msg += 'an '; + msg += `${other[0]}`; + } + } + + if (actual == null) { + msg += `. Received ${actual}`; + } else if (typeof actual === 'function' && actual.name) { + msg += `. Received function ${actual.name}`; + } else if (typeof actual === 'object') { + if (actual.constructor && actual.constructor.name) { + msg += `. Received an instance of ${actual.constructor.name}`; + } else { + const inspected = lazyInternalUtilInspect() + .inspect(actual, { depth: -1 }); + msg += `. Received ${inspected}`; + } + } else { + let inspected = lazyInternalUtilInspect() + .inspect(actual, { colors: false }); + if (inspected.length > 25) + inspected = `${inspected.slice(0, 25)}...`; + msg += `. Received type ${typeof actual} (${inspected})`; + } + return msg; +}; + module.exports = { addCodeToName, // Exported for NghttpError codes, @@ -931,112 +1039,9 @@ E('ERR_INVALID_ADDRESS_FAMILY', function(addressType, host, port) { this.port = port; return `Invalid address family: ${addressType} ${host}:${port}`; }, RangeError); -E('ERR_INVALID_ARG_TYPE', - (name, expected, actual) => { - assert(typeof name === 'string', "'name' must be a string"); - if (!ArrayIsArray(expected)) { - expected = [expected]; - } - - let msg = 'The '; - if (name.endsWith(' argument')) { - // For cases like 'first argument' - msg += `${name} `; - } else { - const type = name.includes('.') ? 'property' : 'argument'; - msg += `"${name}" ${type} `; - } - msg += 'must be '; - - const types = []; - const instances = []; - const other = []; - - for (const value of expected) { - assert(typeof value === 'string', - 'All expected entries have to be of type string'); - if (kTypes.includes(value)) { - types.push(value.toLowerCase()); - } else if (classRegExp.test(value)) { - instances.push(value); - } else { - assert(value !== 'object', - 'The value "object" should be written as "Object"'); - other.push(value); - } - } - - // Special handle `object` in case other instances are allowed to outline - // the differences between each other. - if (instances.length > 0) { - const pos = types.indexOf('object'); - if (pos !== -1) { - types.splice(pos, 1); - instances.push('Object'); - } - } - - if (types.length > 0) { - if (types.length > 2) { - const last = types.pop(); - msg += `one of type ${types.join(', ')}, or ${last}`; - } else if (types.length === 2) { - msg += `one of type ${types[0]} or ${types[1]}`; - } else { - msg += `of type ${types[0]}`; - } - if (instances.length > 0 || other.length > 0) - msg += ' or '; - } - - if (instances.length > 0) { - if (instances.length > 2) { - const last = instances.pop(); - msg += `an instance of ${instances.join(', ')}, or ${last}`; - } else { - msg += `an instance of ${instances[0]}`; - if (instances.length === 2) { - msg += ` or ${instances[1]}`; - } - } - if (other.length > 0) - msg += ' or '; - } - - if (other.length > 0) { - if (other.length > 2) { - const last = other.pop(); - msg += `one of ${other.join(', ')}, or ${last}`; - } else if (other.length === 2) { - msg += `one of ${other[0]} or ${other[1]}`; - } else { - if (other[0].toLowerCase() !== other[0]) - msg += 'an '; - msg += `${other[0]}`; - } - } - - if (actual == null) { - msg += `. Received ${actual}`; - } else if (typeof actual === 'function' && actual.name) { - msg += `. Received function ${actual.name}`; - } else if (typeof actual === 'object') { - if (actual.constructor && actual.constructor.name) { - msg += `. Received an instance of ${actual.constructor.name}`; - } else { - const inspected = lazyInternalUtilInspect() - .inspect(actual, { depth: -1 }); - msg += `. Received ${inspected}`; - } - } else { - let inspected = lazyInternalUtilInspect() - .inspect(actual, { colors: false }); - if (inspected.length > 25) - inspected = `${inspected.slice(0, 25)}...`; - msg += `. Received type ${typeof actual} (${inspected})`; - } - return msg; - }, TypeError); +E('ERR_INVALID_ARG_TYPE', (name, expected, actual) => + invalidValueTypeError(name, expected, actual), + TypeError); E('ERR_INVALID_ARG_VALUE', (name, value, reason = 'is invalid') => { let inspected = lazyInternalUtilInspect().inspect(value); if (inspected.length > 128) { @@ -1070,6 +1075,9 @@ E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s', TypeError); E('ERR_INVALID_HANDLE_TYPE', 'This handle type cannot be sent', TypeError); E('ERR_INVALID_HTTP_TOKEN', '%s must be a valid HTTP token ["%s"]', TypeError); E('ERR_INVALID_IP_ADDRESS', 'Invalid IP address: %s', TypeError); +E('ERR_INVALID_OPT_TYPE', (name, expected, actual) => + invalidValueTypeError(name, expected, actual, 'option'), + TypeError); E('ERR_INVALID_OPT_VALUE', (name, value) => `The value "${String(value)}" is invalid for option "${name}"`, TypeError, diff --git a/lib/internal/validators.js b/lib/internal/validators.js index 329410ef3d8d0a..8d8e12ebea300a 100644 --- a/lib/internal/validators.js +++ b/lib/internal/validators.js @@ -11,6 +11,8 @@ const { codes: { ERR_INVALID_ARG_TYPE, ERR_INVALID_ARG_VALUE, + ERR_INVALID_OPT_TYPE, + ERR_INVALID_OPT_VALUE, ERR_OUT_OF_RANGE, ERR_UNKNOWN_SIGNAL } @@ -67,65 +69,84 @@ function parseFileMode(value, name, def) { throw new ERR_INVALID_ARG_VALUE(name, value, modeDesc); } +const handleValueName = (name) => { + let TypeErr = ERR_INVALID_ARG_TYPE; + let ValueErr = ERR_INVALID_ARG_VALUE; + if (typeof name === 'object') { + if (name.type === 'option') { + TypeErr = ERR_INVALID_OPT_TYPE; + ValueErr = ERR_INVALID_OPT_VALUE; + } + name = name.name; + } + return { valueName: name, TypeErr, ValueErr }; +}; + const validateInteger = hideStackFrames( (value, name, min = NumberMIN_SAFE_INTEGER, max = NumberMAX_SAFE_INTEGER) => { + const { valueName, TypeErr } = handleValueName(name); if (typeof value !== 'number') - throw new ERR_INVALID_ARG_TYPE(name, 'number', value); + throw new TypeErr(valueName, 'number', value); if (!NumberIsInteger(value)) - throw new ERR_OUT_OF_RANGE(name, 'an integer', value); + throw new ERR_OUT_OF_RANGE(valueName, 'an integer', value); if (value < min || value > max) - throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + throw new ERR_OUT_OF_RANGE(valueName, `>= ${min} && <= ${max}`, value); } ); const validateInt32 = hideStackFrames( (value, name, min = -2147483648, max = 2147483647) => { // The defaults for min and max correspond to the limits of 32-bit integers. + const { valueName, TypeErr } = handleValueName(name); if (!isInt32(value)) { if (typeof value !== 'number') { - throw new ERR_INVALID_ARG_TYPE(name, 'number', value); + throw new TypeErr(valueName, 'number', value); } if (!NumberIsInteger(value)) { - throw new ERR_OUT_OF_RANGE(name, 'an integer', value); + throw new ERR_OUT_OF_RANGE(valueName, 'an integer', value); } - throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + throw new ERR_OUT_OF_RANGE(valueName, `>= ${min} && <= ${max}`, value); } if (value < min || value > max) { - throw new ERR_OUT_OF_RANGE(name, `>= ${min} && <= ${max}`, value); + throw new ERR_OUT_OF_RANGE(valueName, `>= ${min} && <= ${max}`, value); } } ); const validateUint32 = hideStackFrames((value, name, positive) => { + const { valueName, TypeErr } = handleValueName(name); if (!isUint32(value)) { if (typeof value !== 'number') { - throw new ERR_INVALID_ARG_TYPE(name, 'number', value); + throw new TypeErr(valueName, 'number', value); } if (!NumberIsInteger(value)) { - throw new ERR_OUT_OF_RANGE(name, 'an integer', value); + throw new ERR_OUT_OF_RANGE(valueName, 'an integer', value); } const min = positive ? 1 : 0; // 2 ** 32 === 4294967296 - throw new ERR_OUT_OF_RANGE(name, `>= ${min} && < 4294967296`, value); + throw new ERR_OUT_OF_RANGE(valueName, `>= ${min} && < 4294967296`, value); } if (positive && value === 0) { - throw new ERR_OUT_OF_RANGE(name, '>= 1 && < 4294967296', value); + throw new ERR_OUT_OF_RANGE(valueName, '>= 1 && < 4294967296', value); } }); function validateString(value, name) { + const { valueName, TypeErr } = handleValueName(name); if (typeof value !== 'string') - throw new ERR_INVALID_ARG_TYPE(name, 'string', value); + throw new TypeErr(valueName, 'string', value); } function validateNumber(value, name) { + const { valueName, TypeErr } = handleValueName(name); if (typeof value !== 'number') - throw new ERR_INVALID_ARG_TYPE(name, 'number', value); + throw new TypeErr(valueName, 'number', value); } function validateSignalName(signal, name = 'signal') { + const { valueName, TypeErr } = handleValueName(name); if (typeof signal !== 'string') - throw new ERR_INVALID_ARG_TYPE(name, 'string', signal); + throw new TypeErr(valueName, 'string', signal); if (signals[signal] === undefined) { if (signals[signal.toUpperCase()] !== undefined) { @@ -138,11 +159,9 @@ function validateSignalName(signal, name = 'signal') { } const validateBuffer = hideStackFrames((buffer, name = 'buffer') => { - if (!isArrayBufferView(buffer)) { - throw new ERR_INVALID_ARG_TYPE(name, - ['Buffer', 'TypedArray', 'DataView'], - buffer); - } + const { valueName, TypeErr } = handleValueName(name); + if (!isArrayBufferView(buffer)) + throw new TypeErr(valueName, ['Buffer', 'TypedArray', 'DataView'], buffer); }); function validateEncoding(data, encoding) {