From 7a1b47f329f2e6481ef8f54951570197fd98378d Mon Sep 17 00:00:00 2001 From: James M Snell Date: Wed, 4 May 2016 07:35:30 -0700 Subject: [PATCH] errors: add internal/errors module This is step one to making it so that changes to error messages are not forced to be semver-major changes any more. The internal errors.js module creates three custom error class instances that use an assigned static error code to look up the error message. The message itself can change but the error code would remain the same. Changes to error code would still be considered semver-major, but changes to the messages themselves would not be. This also updates the Error objects created in lib/internal to use the new mechanism, changing and streamlining several of the error messages in the process. Obviously, this is a semver-major change on it's own. Several test cases were also modified and a new common.throws method added to test/common.js that understands the new code mechanism. This is added common.js so that the existing assert module api is unchanged. --- lib/_debugger.js | 3 +- lib/_http_client.js | 16 +- lib/_http_outgoing.js | 31 +- lib/_http_server.js | 6 +- lib/_stream_readable.js | 21 +- lib/_stream_writable.js | 32 +- lib/_tls_wrap.js | 3 +- lib/assert.js | 3 +- lib/buffer.js | 133 ++++++--- lib/child_process.js | 23 +- lib/console.js | 5 +- lib/crypto.js | 12 +- lib/dgram.js | 14 +- lib/dns.js | 17 +- lib/events.js | 30 +- lib/fs.js | 49 ++- lib/internal/bootstrap_node.js | 8 +- lib/internal/child_process.js | 32 +- lib/internal/errors.js | 282 ++++++++++++++++++ lib/internal/net.js | 3 +- lib/internal/process.js | 30 +- lib/internal/process/next_tick.js | 6 +- lib/internal/process/stdio.js | 19 +- lib/internal/process/warning.js | 3 +- lib/internal/repl.js | 8 +- lib/internal/socket_list.js | 3 +- lib/internal/util.js | 3 +- lib/net.js | 29 +- lib/path.js | 16 +- lib/readline.js | 7 +- lib/timers.js | 14 +- lib/url.js | 14 +- lib/util.js | 10 +- lib/zlib.js | 13 +- node.gyp | 1 + test/common.js | 23 ++ test/parallel/test-assert.js | 4 +- test/parallel/test-buffer-alloc.js | 11 +- test/parallel/test-buffer-compare-offset.js | 18 +- test/parallel/test-buffer-concat.js | 11 +- test/parallel/test-buffer.js | 11 +- .../test-child-process-spawn-typeerror.js | 14 +- .../test-child-process-spawnsync-input.js | 6 +- .../test-child-process-validate-stdio.js | 14 +- test/parallel/test-console-instance.js | 10 +- test/parallel/test-dgram-setTTL.js | 4 +- test/parallel/test-dns.js | 22 +- test/parallel/test-file-write-stream3.js | 4 +- test/parallel/test-fs-access.js | 8 +- test/parallel/test-fs-read-stream-inherit.js | 4 +- .../test-fs-read-stream-throw-type-error.js | 20 +- test/parallel/test-fs-read-stream.js | 4 +- test/parallel/test-fs-watchfile.js | 4 +- .../test-fs-write-stream-throw-type-error.js | 20 +- test/parallel/test-http-agent-keepalive.js | 2 +- ....parse-only-support-http-https-protocol.js | 82 ++--- test/parallel/test-http-write-head.js | 4 +- test/parallel/test-internal-errors.js | 106 +++++++ test/parallel/test-net-create-connection.js | 30 +- test/parallel/test-net-listen-port-option.js | 4 +- test/parallel/test-net-socket-write-error.js | 5 +- test/parallel/test-path-parse-format.js | 5 +- test/parallel/test-process-emitwarning.js | 4 +- test/parallel/test-readline-interface.js | 13 +- test/parallel/test-regress-GH-5727.js | 8 +- .../test-timers-throw-when-cb-not-function.js | 75 +++-- test/parallel/test-tls-basic-validations.js | 6 +- test/parallel/test-tty-stdout-end.js | 14 +- 68 files changed, 983 insertions(+), 486 deletions(-) create mode 100644 lib/internal/errors.js create mode 100644 test/parallel/test-internal-errors.js diff --git a/lib/_debugger.js b/lib/_debugger.js index e872d77ea55962..5a104e3840faa2 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -126,7 +126,8 @@ Protocol.prototype.execute = function(d) { break; default: - throw new Error('Unknown state'); + const errors = require('internal/errors'); + throw new errors.Error('UNKNOWNSTATE'); } }; diff --git a/lib/_http_client.js b/lib/_http_client.js index 68eb125e0e10e9..3faa98180a8d3a 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const util = require('util'); const net = require('net'); const url = require('url'); @@ -22,7 +23,7 @@ function ClientRequest(options, cb) { if (typeof options === 'string') { options = url.parse(options); if (!options.hostname) { - throw new Error('Unable to determine the domain name'); + throw new errors.Error('DNSUNKNOWNDOMAIN'); } } else { options = util._extend({}, options); @@ -50,10 +51,11 @@ function ClientRequest(options, cb) { // well, and b) possibly too restrictive for real-world usage. That's // why it only scans for spaces because those are guaranteed to create // an invalid request. - throw new TypeError('Request path contains unescaped characters'); + throw new errors.TypeError('HTTPINVALIDPATH'); } else if (protocol !== expectedProtocol) { - throw new Error('Protocol "' + protocol + '" not supported. ' + - 'Expected "' + expectedProtocol + '"'); + throw new errors.Error('HTTPUNSUPPORTEDPROTOCOL', + protocol, + expectedProtocol); } const defaultPort = options.defaultPort || @@ -70,7 +72,7 @@ function ClientRequest(options, cb) { var method = self.method = (options.method || 'GET').toUpperCase(); if (!common._checkIsHttpToken(method)) { - throw new TypeError('Method must be a valid HTTP token'); + throw new errors.TypeError('HTTPMETHODINVALID'); } self.path = options.path || '/'; if (cb) { @@ -247,9 +249,7 @@ function emitAbortNT(self) { function createHangUpError() { - var error = new Error('socket hang up'); - error.code = 'ECONNRESET'; - return error; + return new errors.Error('ECONNRESET'); } diff --git a/lib/_http_outgoing.js b/lib/_http_outgoing.js index e1e78e010cfd62..8c6f42e3f6b682 100644 --- a/lib/_http_outgoing.js +++ b/lib/_http_outgoing.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const assert = require('assert').ok; const Stream = require('stream'); const timers = require('timers'); @@ -302,11 +303,10 @@ OutgoingMessage.prototype._storeHeader = function(firstLine, headers) { function storeHeader(self, state, field, value) { if (!common._checkIsHttpToken(field)) { - throw new TypeError( - 'Header name must be a valid HTTP Token ["' + field + '"]'); + throw new errors.TypeError('HTTPINVALIDTOKEN', field); } if (common._checkInvalidHeaderChar(value) === true) { - throw new TypeError('The header content contains invalid characters'); + throw new errors.TypeError('HTTPINVALIDHEADER'); } state.messageHeader += field + ': ' + escapeHeaderValue(value) + CRLF; @@ -336,17 +336,15 @@ function storeHeader(self, state, field, value) { OutgoingMessage.prototype.setHeader = function(name, value) { if (!common._checkIsHttpToken(name)) - throw new TypeError( - 'Header name must be a valid HTTP Token ["' + name + '"]'); + throw new errors.TypeError('HTTPINVALIDTOKEN', name); if (typeof name !== 'string') - throw new TypeError('"name" should be a string in setHeader(name, value)'); + throw new errors.TypeError('INVALIDARG', 'name', 'string'); if (value === undefined) - throw new Error('"value" required in setHeader("' + name + '", value)'); + throw new errors.Error('REQUIREDARG', 'value'); if (this._header) - throw new Error('Can\'t set headers after they are sent.'); - if (common._checkInvalidHeaderChar(value) === true) { - throw new TypeError('The header content contains invalid characters'); - } + throw new errors.Error('HEADERSSENT'); + if (common._checkInvalidHeaderChar(value) === true) + throw new errors.TypeError('HTTPINVALIDHEADER'); if (this._headers === null) this._headers = {}; @@ -361,7 +359,7 @@ OutgoingMessage.prototype.setHeader = function(name, value) { OutgoingMessage.prototype.getHeader = function(name) { if (arguments.length < 1) { - throw new Error('"name" argument is required for getHeader(name)'); + throw new errors.Error('REQUIREDARG', 'name'); } if (!this._headers) return; @@ -440,7 +438,7 @@ OutgoingMessage.prototype.write = function(chunk, encoding, callback) { } if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) { - throw new TypeError('First argument must be a string or Buffer'); + throw new errors.TypeError('INVALIDARG', 'chunk', ['Buffer', 'string']); } @@ -515,11 +513,10 @@ OutgoingMessage.prototype.addTrailers = function(headers) { value = headers[key]; } if (!common._checkIsHttpToken(field)) { - throw new TypeError( - 'Trailer name must be a valid HTTP Token ["' + field + '"]'); + throw new errors.TypeError('HTTPINVALIDTOKEN', field); } if (common._checkInvalidHeaderChar(value) === true) { - throw new TypeError('The trailer content contains invalid characters'); + throw new errors.TypeError('HTTPINVALIDTRAILER'); } this._trailer += field + ': ' + escapeHeaderValue(value) + CRLF; } @@ -539,7 +536,7 @@ OutgoingMessage.prototype.end = function(data, encoding, callback) { } if (data && typeof data !== 'string' && !(data instanceof Buffer)) { - throw new TypeError('First argument must be a string or Buffer'); + throw new errors.TypeError('INVALIDARG', 'data', ['Buffer', 'string']); } if (this.finished) { diff --git a/lib/_http_server.js b/lib/_http_server.js index d0f0fbe5d5bb38..25a3384a2edf7d 100644 --- a/lib/_http_server.js +++ b/lib/_http_server.js @@ -188,8 +188,10 @@ ServerResponse.prototype.writeHead = function(statusCode, reason, obj) { } statusCode |= 0; - if (statusCode < 100 || statusCode > 999) - throw new RangeError(`Invalid status code: ${statusCode}`); + if (statusCode < 100 || statusCode > 999) { + const errors = require('internal/errors'); + throw new errors.RangeError('HTTPINVALIDSTATUS', statusCode); + } var statusLine = 'HTTP/1.1 ' + statusCode.toString() + ' ' + this.statusMessage + CRLF; diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js index afe3b3baf065c2..4eb90161702a0a 100644 --- a/lib/_stream_readable.js +++ b/lib/_stream_readable.js @@ -12,6 +12,25 @@ var StringDecoder; util.inherits(Readable, Stream); +// This does not use the internal/errors so that it can remain +// portable with the readable-streams module. +function typeError(code, args) { + const assert = require('assert'); + var msg; + switch (code) { + case 'EINVALIDCHUNK': + msg = 'Invalid non-string/buffer chunk'; + break; + default: + assert.fail(null, null, 'Unknown error code.'); + } + var error = new TypeError(msg); + Error.captureStackTrace(error, typeError); + error.code = error.errno = code; + error.name = 'TypeError[' + code + ']'; + return error; +} + const hasPrependListener = typeof EE.prototype.prependListener === 'function'; function prependListener(emitter, event, fn) { @@ -396,7 +415,7 @@ function chunkInvalid(state, chunk) { chunk !== null && chunk !== undefined && !state.objectMode) { - er = new TypeError('Invalid non-string/buffer chunk'); + er = new typeError('EINVALIDCHUNK'); } return er; } diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js index 76c4972d405c81..bf6426f445a636 100644 --- a/lib/_stream_writable.js +++ b/lib/_stream_writable.js @@ -16,6 +16,32 @@ util.inherits(Writable, Stream); function nop() {} +// This does not use the internal/errors so that it can remain +// portable with the readable-streams module. +function typeError(code, args) { + const assert = require('assert'); + var msg; + switch (code) { + case 'EWRITENULL': + msg = 'May not write null values to stream.'; + break; + case 'EINVALIDCHUNK': + msg = 'Invalid non-string/buffer chunk'; + break; + case 'UNKNOWNENCODING': + assert(args && args[0]); + msg = 'Unknown encoding: ' + args[0]; + break; + default: + assert.fail(null, null, 'Unknown error code.'); + } + var error = new TypeError(msg); + Error.captureStackTrace(error, typeError); + error.code = error.errno = code; + error.name = 'TypeError[' + code + ']'; + return error; +} + function WriteReq(chunk, encoding, cb) { this.chunk = chunk; this.encoding = encoding; @@ -181,12 +207,12 @@ function validChunk(stream, state, chunk, cb) { // if we are not in object mode then throw // if it is not a buffer, string, or undefined. if (chunk === null) { - er = new TypeError('May not write null values to stream'); + er = new typeError('EWRITENULL'); } else if (!(chunk instanceof Buffer) && typeof chunk !== 'string' && chunk !== undefined && !state.objectMode) { - er = new TypeError('Invalid non-string/buffer chunk'); + er = new typeError('EINVALIDCHUNK'); } if (er) { stream.emit('error', er); @@ -249,7 +275,7 @@ Writable.prototype.setDefaultEncoding = function setDefaultEncoding(encoding) { if (typeof encoding === 'string') encoding = encoding.toLowerCase(); if (!Buffer.isEncoding(encoding)) - throw new TypeError('Unknown encoding: ' + encoding); + throw new typeError('UNKNOWNENCODING', [encoding]); this._writableState.defaultEncoding = encoding; return this; }; diff --git a/lib/_tls_wrap.js b/lib/_tls_wrap.js index 029d9ac26fe6bd..4fb9021829438b 100644 --- a/lib/_tls_wrap.js +++ b/lib/_tls_wrap.js @@ -762,7 +762,8 @@ function Server(/* [options], listener */) { var timeout = options.handshakeTimeout || (120 * 1000); if (typeof timeout !== 'number') { - throw new TypeError('handshakeTimeout must be a number'); + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDOPT', 'handshakeTimeout', 'number'); } if (self.sessionTimeout) { diff --git a/lib/assert.js b/lib/assert.js index 8955aa8761d7c2..1ac9a6e403b317 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -326,7 +326,8 @@ function _throws(shouldThrow, block, expected, message) { var actual; if (typeof block !== 'function') { - throw new TypeError('"block" argument must be a function'); + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'block', 'function'); } if (typeof expected === 'string') { diff --git a/lib/buffer.js b/lib/buffer.js index 515f841fd36507..8dec15ab3bf7c7 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -9,9 +9,6 @@ exports.SlowBuffer = SlowBuffer; exports.INSPECT_MAX_BYTES = 50; exports.kMaxLength = binding.kMaxLength; -const kFromErrorMsg = 'First argument must be a string, Buffer, ' + - 'ArrayBuffer, Array, or array-like object.'; - Buffer.poolSize = 8 * 1024; var poolSize, poolOffset, allocPool; @@ -32,8 +29,10 @@ Buffer.prototype.swap16 = function swap16() { // do the swap in javascript. For larger buffers, // dropping down to the native code is faster. const len = this.length; - if (len % 2 !== 0) - throw new RangeError('Buffer size must be a multiple of 16-bits'); + if (len % 2 !== 0) { + const errors = require('internal/errors'); + throw new errors.RangeError('BUFFERSWAP16'); + } if (len < 512) { for (var i = 0; i < len; i += 2) swap(this, i, i + 1); @@ -47,8 +46,10 @@ Buffer.prototype.swap32 = function swap32() { // do the swap in javascript. For larger buffers, // dropping down to the native code is faster. const len = this.length; - if (len % 4 !== 0) - throw new RangeError('Buffer size must be a multiple of 32-bits'); + if (len % 4 !== 0) { + const errors = require('internal/errors'); + throw new errors.RangeError('BUFFERSWAP32'); + } if (len < 1024) { for (var i = 0; i < len; i += 4) { swap(this, i, i + 3); @@ -121,8 +122,10 @@ function Buffer(arg, encodingOrOffset, length) { * Buffer.from(arrayBuffer[, byteOffset[, length]]) **/ Buffer.from = function(value, encodingOrOffset, length) { - if (typeof value === 'number') - throw new TypeError('"value" argument must not be a number'); + if (typeof value === 'number') { + const errors = require('internal/errors'); + throw new errors.TypeError('ARGNOTNUM', 'value'); + } if (value instanceof ArrayBuffer) return fromArrayBuffer(value, encodingOrOffset, length); @@ -138,7 +141,8 @@ Object.setPrototypeOf(Buffer, Uint8Array); function assertSize(size) { if (typeof size !== 'number') { - const err = new TypeError('"size" argument must be a number'); + const errors = require('internal/errors'); + const err = new errors.TypeError('INVALIDARG', 'size', 'number'); // The following hides the 'assertSize' method from the // callstack. This is done simply to hide the internal // details of the implementation from bleeding out to users. @@ -222,8 +226,10 @@ function fromString(string, encoding) { if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8'; - if (!Buffer.isEncoding(encoding)) - throw new TypeError('"encoding" must be a valid string encoding'); + if (!Buffer.isEncoding(encoding)) { + const errors = require('internal/errors'); + throw new errors.TypeError('UNKNOWNENCODING', encoding); + } var length = byteLength(string, encoding); @@ -284,7 +290,10 @@ function fromObject(obj) { } } - throw new TypeError(kFromErrorMsg); + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 1, + ['Buffer', 'ArrayBuffer', 'Array', + 'array-like object']); } @@ -296,9 +305,13 @@ Buffer.isBuffer = function isBuffer(b) { Buffer.compare = function compare(a, b) { - if (!(a instanceof Buffer) || - !(b instanceof Buffer)) { - throw new TypeError('Arguments must be Buffers'); + if (!(a instanceof Buffer)) { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'a', 'Buffer'); + } + if (!(b instanceof Buffer)) { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'b', 'Buffer'); } if (a === b) { @@ -337,8 +350,10 @@ Buffer.isEncoding = function(encoding) { Buffer.concat = function(list, length) { var i; - if (!Array.isArray(list)) - throw new TypeError('"list" argument must be an Array of Buffers'); + if (!Array.isArray(list)) { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'list', 'Array of Buffers'); + } if (list.length === 0) return Buffer.alloc(0); @@ -355,8 +370,10 @@ Buffer.concat = function(list, length) { var pos = 0; for (i = 0; i < list.length; i++) { var buf = list[i]; - if (!Buffer.isBuffer(buf)) - throw new TypeError('"list" argument must be an Array of Buffers'); + if (!Buffer.isBuffer(buf)) { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'list', 'Array of Buffers'); + } buf.copy(buffer, pos); pos += buf.length; } @@ -508,8 +525,10 @@ function slowToString(encoding, start, end) { return this.ucs2Slice(start, end); default: - if (loweredCase) - throw new TypeError('Unknown encoding: ' + encoding); + if (loweredCase) { + const errors = require('internal/errors'); + throw new errors.TypeError('UNKNOWNENCODING', encoding); + } encoding = (encoding + '').toLowerCase(); loweredCase = true; } @@ -531,8 +550,10 @@ Buffer.prototype.toString = function() { Buffer.prototype.equals = function equals(b) { - if (!(b instanceof Buffer)) - throw new TypeError('Argument must be a Buffer'); + if (!(b instanceof Buffer)) { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 1, 'Buffer'); + } if (this === b) return true; @@ -559,8 +580,10 @@ Buffer.prototype.compare = function compare(target, thisStart, thisEnd) { - if (!(target instanceof Buffer)) - throw new TypeError('Argument must be a Buffer'); + if (!(target instanceof Buffer)) { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'target', 'Buffer'); + } if (start === undefined) start = 0; @@ -575,7 +598,8 @@ Buffer.prototype.compare = function compare(target, end > target.length || thisStart < 0 || thisEnd > this.length) { - throw new RangeError('out of range index'); + const errors = require('internal/errors'); + throw new errors.RangeError('INDEXOUTOFRANGE'); } if (thisStart >= thisEnd && start >= end) @@ -630,7 +654,9 @@ function bidirectionalIndexOf(buffer, val, byteOffset, encoding, dir) { return binding.indexOfNumber(buffer, val, byteOffset, dir); } - throw new TypeError('"val" argument must be string, number or Buffer'); + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'val', + ['Buffer', 'string', 'number']); } @@ -655,7 +681,8 @@ function slowIndexOf(buffer, val, byteOffset, encoding, dir) { default: if (loweredCase) { - throw new TypeError('Unknown encoding: ' + encoding); + const errors = require('internal/errors'); + throw new errors.TypeError('UNKNOWNENCODING', encoding); } encoding = ('' + encoding).toLowerCase(); @@ -706,10 +733,12 @@ Buffer.prototype.fill = function fill(val, start, end, encoding) { val = 0; } if (encoding !== undefined && typeof encoding !== 'string') { - throw new TypeError('encoding must be a string'); + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'encoding', 'string'); } if (typeof encoding === 'string' && !Buffer.isEncoding(encoding)) { - throw new TypeError('Unknown encoding: ' + encoding); + const errors = require('internal/errors'); + throw new errors.TypeError('UNKNOWNENCODING', encoding); } } else if (typeof val === 'number') { @@ -717,8 +746,10 @@ Buffer.prototype.fill = function fill(val, start, end, encoding) { } // Invalid ranges are not set to a default, so can range check early. - if (start < 0 || end > this.length) - throw new RangeError('Out of range index'); + if (start < 0 || end > this.length) { + const errors = require('internal/errors'); + throw new errors.RangeError('INDEXOUTOFRANGE'); + } if (end <= start) return this; @@ -768,8 +799,10 @@ Buffer.prototype.write = function(string, offset, length, encoding) { if (length === undefined || length > remaining) length = remaining; - if (string.length > 0 && (length < 0 || offset < 0)) - throw new RangeError('Attempt to write outside buffer bounds'); + if (string.length > 0 && (length < 0 || offset < 0)) { + const errors = require('internal/errors'); + throw new errors.RangeError('WRITEOUTOFBOUNDS'); + } if (!encoding) encoding = 'utf8'; @@ -801,8 +834,10 @@ Buffer.prototype.write = function(string, offset, length, encoding) { return this.ucs2Write(string, offset, length); default: - if (loweredCase) - throw new TypeError('Unknown encoding: ' + encoding); + if (loweredCase) { + const errors = require('internal/errors'); + throw new errors.TypeError('UNKNOWNENCODING', encoding); + } encoding = ('' + encoding).toLowerCase(); loweredCase = true; } @@ -826,8 +861,10 @@ Buffer.prototype.slice = function slice(start, end) { function checkOffset(offset, ext, length) { - if (offset + ext > length) - throw new RangeError('Index out of range'); + if (offset + ext > length) { + const errors = require('internal/errors'); + throw new errors.RangeError('INDEXOUTOFRANGE'); + } } @@ -1034,12 +1071,18 @@ Buffer.prototype.readDoubleBE = function readDoubleBE(offset, noAssert) { function checkInt(buffer, value, offset, ext, max, min) { - if (!(buffer instanceof Buffer)) - throw new TypeError('"buffer" argument must be a Buffer instance'); - if (value > max || value < min) - throw new TypeError('"value" argument is out of bounds'); - if (offset + ext > buffer.length) - throw new RangeError('Index out of range'); + if (!(buffer instanceof Buffer)) { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'buffer', 'Buffer'); + } + if (value > max || value < min) { + const errors = require('internal/errors'); + throw new errors.TypeError('OUTOFBOUNDSARG', 'value'); + } + if (offset + ext > buffer.length) { + const errors = require('internal/errors'); + throw new errors.RangeError('INDEXOUTOFRANGE'); + } } diff --git a/lib/child_process.js b/lib/child_process.js index 81fb8c1fcd0fe8..504b35aee2d460 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const util = require('util'); const internalUtil = require('internal/util'); const debug = util.debuglog('child_process'); @@ -24,7 +25,7 @@ exports.fork = function(modulePath /*, args, options*/) { args = arguments[1]; options = util._extend({}, arguments[2]); } else if (arguments[1] && typeof arguments[1] !== 'object') { - throw new TypeError('Incorrect value of args option'); + throw new errors.TypeError('INVALIDARG', 'args', 'Array'); } else { args = []; options = util._extend({}, arguments[1]); @@ -133,7 +134,7 @@ exports.execFile = function(file /*, args, options, callback*/) { } if (pos === 1 && arguments.length > 1) { - throw new TypeError('Incorrect value of args option'); + throw new errors.TypeError('INVALIDARG', 'args', 'Array'); } var child = spawn(file, args, { @@ -309,7 +310,7 @@ function normalizeSpawnArguments(file /*, args, options*/) { options = arguments[2]; } else if (arguments[1] !== undefined && (arguments[1] === null || typeof arguments[1] !== 'object')) { - throw new TypeError('Incorrect value of args option'); + throw new errors.TypeError('INVALIDARG', 'args', 'Array'); } else { args = []; options = arguments[1]; @@ -318,7 +319,7 @@ function normalizeSpawnArguments(file /*, args, options*/) { if (options === undefined) options = {}; else if (options === null || typeof options !== 'object') - throw new TypeError('"options" argument must be an object'); + throw new errors.TypeError('INVALIDARG', 'options', 'object'); // Make a shallow copy so we don't clobber the user's options object. options = Object.assign({}, options); @@ -419,15 +420,15 @@ function spawnSync(/*file, args, options*/) { var input = options.stdio[i] && options.stdio[i].input; if (input != null) { var pipe = options.stdio[i] = util._extend({}, options.stdio[i]); - if (Buffer.isBuffer(input)) + if (Buffer.isBuffer(input)) { pipe.input = input; - else if (typeof input === 'string') + } else if (typeof input === 'string') { pipe.input = Buffer.from(input, options.encoding); - else - throw new TypeError(util.format( - 'stdio[%d] should be Buffer or string not %s', - i, - typeof input)); + } else { + throw new errors.TypeError('INVALIDOPT', + `stdio[${i}]`, + ['Buffer', 'string']); + } } } diff --git a/lib/console.js b/lib/console.js index 2359cb36e011d0..1556d9cf084e08 100644 --- a/lib/console.js +++ b/lib/console.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const util = require('util'); function Console(stdout, stderr) { @@ -7,12 +8,12 @@ function Console(stdout, stderr) { return new Console(stdout, stderr); } if (!stdout || typeof stdout.write !== 'function') { - throw new TypeError('Console expects a writable stream instance'); + throw new errors.TypeError('CONSOLEWRITABLE'); } if (!stderr) { stderr = stdout; } else if (typeof stderr.write !== 'function') { - throw new TypeError('Console expects writable stream instances'); + throw new errors.TypeError('CONSOLEWRITABLE'); } var prop = { diff --git a/lib/crypto.js b/lib/crypto.js index 688ac34e4ad767..58f5467e919233 100644 --- a/lib/crypto.js +++ b/lib/crypto.js @@ -3,6 +3,7 @@ 'use strict'; +const errors = require('internal/errors'); const internalUtil = require('internal/util'); internalUtil.assertCrypto(exports); @@ -341,7 +342,8 @@ function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) { if (!(sizeOrKey instanceof Buffer) && typeof sizeOrKey !== 'number' && typeof sizeOrKey !== 'string') - throw new TypeError('First argument should be number, string or Buffer'); + throw new errors.TypeError('INVALIDARG', 1, + ['Buffer', 'string', 'number']); if (keyEncoding) { if (typeof keyEncoding !== 'string' || @@ -483,7 +485,7 @@ DiffieHellman.prototype.setPrivateKey = function(key, encoding) { function ECDH(curve) { if (typeof curve !== 'string') - throw new TypeError('"curve" argument should be a string'); + throw new errors.TypeError('INVALIDARG', 'curve', 'string'); this._handle = new binding.ECDH(curve); } @@ -516,7 +518,7 @@ ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) { else if (format === 'uncompressed') f = constants.POINT_CONVERSION_UNCOMPRESSED; else - throw new TypeError('Bad format: ' + format); + throw new errors.TypeError('INVALIDARGVALUE', 'format', format); } else { f = constants.POINT_CONVERSION_UNCOMPRESSED; } @@ -609,10 +611,10 @@ Certificate.prototype.exportChallenge = function(object, encoding) { exports.setEngine = function setEngine(id, flags) { if (typeof id !== 'string') - throw new TypeError('"id" argument should be a string'); + throw new errors.TypeError('INVALIDARG', 'id', 'string'); if (flags && typeof flags !== 'number') - throw new TypeError('"flags" argument should be a number, if present'); + throw new errors.TypeError('INVALIDARG', 'flags', 'number'); flags = flags >>> 0; // Use provided engine for everything by default diff --git a/lib/dgram.js b/lib/dgram.js index 4473282148fdff..cce448c3a1b2cf 100644 --- a/lib/dgram.js +++ b/lib/dgram.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const assert = require('assert'); const Buffer = require('buffer').Buffer; const util = require('util'); @@ -254,7 +255,7 @@ function sliceBuffer(buffer, offset, length) { if (typeof buffer === 'string') buffer = Buffer.from(buffer); else if (!(buffer instanceof Buffer)) - throw new TypeError('First argument must be a buffer or string'); + throw new errors.TypeError('INVALIDARG', 'buffer', ['Buffer', 'string']); offset = offset >>> 0; length = length >>> 0; @@ -320,17 +321,18 @@ Socket.prototype.send = function(buffer, if (typeof buffer === 'string') { buffer = [ Buffer.from(buffer) ]; } else if (!(buffer instanceof Buffer)) { - throw new TypeError('First argument must be a buffer or a string'); + throw new errors.TypeError('INVALIDARG', 'buffer', + ['Buffer', 'string', 'Array']); } else { buffer = [ buffer ]; } } else if (!fixBuffer(buffer)) { - throw new TypeError('Buffer list arguments must be buffers or strings'); + throw new errors.TypeError('BUFFERLIST'); } port = port >>> 0; if (port === 0 || port > 65535) - throw new RangeError('Port should be > 0 and < 65536'); + throw new errors.RangeError('PORTRANGE'); // Normalize callback so it's either a function or undefined but not anything // else. @@ -441,7 +443,7 @@ Socket.prototype.setBroadcast = function(arg) { Socket.prototype.setTTL = function(arg) { if (typeof arg !== 'number') { - throw new TypeError('Argument must be a number'); + throw new errors.TypeError('INVALIDARG', 'arg', 'number'); } var err = this._handle.setTTL(arg); @@ -455,7 +457,7 @@ Socket.prototype.setTTL = function(arg) { Socket.prototype.setMulticastTTL = function(arg) { if (typeof arg !== 'number') { - throw new TypeError('Argument must be a number'); + throw new errors.TypeError('INVALIDARG', 'arg', 'number'); } var err = this._handle.setMulticastTTL(arg); diff --git a/lib/dns.js b/lib/dns.js index 8d1541718abf75..8b41f30e853c5f 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -1,6 +1,7 @@ 'use strict'; const util = require('util'); +const errors = require('internal/errors'); const cares = process.binding('cares_wrap'); const uv = process.binding('uv'); @@ -112,13 +113,12 @@ exports.lookup = function lookup(hostname, options, callback) { // Parse arguments if (hostname && typeof hostname !== 'string') { - throw new TypeError('Invalid arguments: ' + - 'hostname must be a string or falsey'); + throw new errors.TypeError('INVALIDARG', 'hostname', ['string', 'falsy']); } else if (typeof options === 'function') { callback = options; family = 0; } else if (typeof callback !== 'function') { - throw new TypeError('Invalid arguments: callback must be passed'); + throw new errors.TypeError('CALLBACKREQUIRED'); } else if (options !== null && typeof options === 'object') { hints = options.hints >>> 0; family = options.family >>> 0; @@ -128,14 +128,15 @@ exports.lookup = function lookup(hostname, options, callback) { hints !== exports.ADDRCONFIG && hints !== exports.V4MAPPED && hints !== (exports.ADDRCONFIG | exports.V4MAPPED)) { - throw new TypeError('Invalid argument: hints must use valid flags'); + throw new errors.TypeError('INVALIDOPTVALUE', 'hints'); } } else { family = options >>> 0; } - if (family !== 0 && family !== 4 && family !== 6) - throw new TypeError('Invalid argument: family must be 4 or 6'); + if (family !== 0 && family !== 4 && family !== 6) { + throw new errors.TypeError('INVALIDFAMILY'); + } callback = makeAsync(callback); @@ -189,10 +190,10 @@ exports.lookupService = function(host, port, callback) { throw new Error('Invalid arguments'); if (isIP(host) === 0) - throw new TypeError('"host" argument needs to be a valid IP address'); + throw new errors.TypeError('INVALIDARG', 'host', 'valid IP address'); if (port == null || !isLegalPort(port)) - throw new TypeError(`"port" should be >= 0 and < 65536, got "${port}"`); + throw new errors.TypeError('PORTRANGE'); port = +port; callback = makeAsync(callback); diff --git a/lib/events.js b/lib/events.js index f0b323e15c1751..8effd8678bd148 100644 --- a/lib/events.js +++ b/lib/events.js @@ -60,8 +60,10 @@ EventEmitter.init = function() { // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) { - if (typeof n !== 'number' || n < 0 || isNaN(n)) - throw new TypeError('"n" argument must be a positive number'); + if (typeof n !== 'number' || n < 0 || isNaN(n)) { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'n', 'positive number'); + } this._maxListeners = n; return this; }; @@ -212,8 +214,10 @@ function _addListener(target, type, listener, prepend) { var events; var existing; - if (typeof listener !== 'function') - throw new TypeError('"listener" argument must be a function'); + if (typeof listener !== 'function') { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'listener', 'function'); + } events = target._events; if (!events) { @@ -291,16 +295,20 @@ function _onceWrap(target, type, listener) { } EventEmitter.prototype.once = function once(type, listener) { - if (typeof listener !== 'function') - throw new TypeError('"listener" argument must be a function'); + if (typeof listener !== 'function') { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'listener', 'function'); + } this.on(type, _onceWrap(this, type, listener)); return this; }; EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) { - if (typeof listener !== 'function') - throw new TypeError('"listener" argument must be a function'); + if (typeof listener !== 'function') { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'listener', 'function'); + } this.prependListener(type, _onceWrap(this, type, listener)); return this; }; @@ -310,8 +318,10 @@ EventEmitter.prototype.removeListener = function removeListener(type, listener) { var list, events, position, i, originalListener; - if (typeof listener !== 'function') - throw new TypeError('"listener" argument must be a function'); + if (typeof listener !== 'function') { + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'listener', 'function'); + } events = this._events; if (!events) diff --git a/lib/fs.js b/lib/fs.js index e441746366e729..e0167d947c15c9 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -3,6 +3,7 @@ 'use strict'; +const errors = require('internal/errors'); const util = require('util'); const pathModule = require('path'); @@ -72,8 +73,7 @@ try { } function throwOptionsError(options) { - throw new TypeError('Expected options to be either an object or a string, ' + - 'but got ' + typeof options + ' instead'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); } function rethrow() { @@ -110,7 +110,7 @@ function makeCallback(cb) { } if (typeof cb !== 'function') { - throw new TypeError('"callback" argument must be a function'); + throw new errors.TypeError('CALLBACKREQUIRED'); } return function() { @@ -219,7 +219,7 @@ fs.access = function(path, mode, callback) { callback = mode; mode = fs.F_OK; } else if (typeof callback !== 'function') { - throw new TypeError('"callback" argument must be a function'); + throw new errors.TypeError('CALLBACKREQUIRED'); } if (!nullCheck(path, callback)) @@ -383,9 +383,7 @@ function readFileAfterStat(err, st) { } if (size > kMaxLength) { - err = new RangeError('File size is greater than possible Buffer: ' + - `0x${kMaxLength.toString(16)} bytes`); - return context.close(err); + return context.close(new errors.RangeError('FILETOOBIG', kMaxLength)); } context.buffer = Buffer.allocUnsafeSlow(size); @@ -926,7 +924,7 @@ fs.readdir = function(path, options, callback) { options = {encoding: options}; } if (typeof options !== 'object') - throw new TypeError('"options" must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); callback = makeCallback(callback); if (!nullCheck(path, callback)) return; @@ -940,7 +938,7 @@ fs.readdirSync = function(path, options) { if (typeof options === 'string') options = {encoding: options}; if (typeof options !== 'object') - throw new TypeError('"options" must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); nullCheck(path); return binding.readdir(pathModule._makeLong(path), options.encoding); }; @@ -990,7 +988,7 @@ fs.readlink = function(path, options, callback) { options = {encoding: options}; } if (typeof options !== 'object') - throw new TypeError('"options" must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); callback = makeCallback(callback); if (!nullCheck(path, callback)) return; var req = new FSReqWrap(); @@ -1003,7 +1001,7 @@ fs.readlinkSync = function(path, options) { if (typeof options === 'string') options = {encoding: options}; if (typeof options !== 'object') - throw new TypeError('"options" must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); nullCheck(path); return binding.readlink(pathModule._makeLong(path), options.encoding); }; @@ -1441,7 +1439,7 @@ fs.watch = function(filename, options, listener) { options = {encoding: options}; } if (typeof options !== 'object') - throw new TypeError('"options" must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); if (options.persistent === undefined) options.persistent = true; if (options.recursive === undefined) options.recursive = false; @@ -1563,7 +1561,7 @@ fs.realpathSync = function realpathSync(path, options) { else if (typeof options === 'string') options = {encoding: options}; else if (typeof options !== 'object') - throw new TypeError('"options" must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); nullCheck(path); return binding.realpath(pathModule._makeLong(path), options.encoding); }; @@ -1578,7 +1576,7 @@ fs.realpath = function realpath(path, options, callback) { } else if (typeof options === 'string') { options = {encoding: options}; } else if (typeof options !== 'object') { - throw new TypeError('"options" must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); } callback = makeCallback(callback); if (!nullCheck(path, callback)) @@ -1614,7 +1612,7 @@ function ReadStream(path, options) { else if (typeof options === 'string') options = { encoding: options }; else if (options === null || typeof options !== 'object') - throw new TypeError('"options" argument must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); // a little bit bigger buffer and water marks by default options = Object.create(options); @@ -1635,16 +1633,17 @@ function ReadStream(path, options) { if (this.start !== undefined) { if (typeof this.start !== 'number') { - throw new TypeError('"start" option must be a Number'); + throw new errors.TypeError('INVALIDOPT', 'start', 'number'); } if (this.end === undefined) { this.end = Infinity; } else if (typeof this.end !== 'number') { - throw new TypeError('"end" option must be a Number'); + throw new errors.TypeError('INVALIDOPT', 'end', 'number'); } if (this.start > this.end) { - throw new Error('"start" option must be <= "end" option'); + throw new errors.Error('INVALIDOPT', 'start', + 'number <= the "end" option'); } this.pos = this.start; @@ -1785,7 +1784,7 @@ function WriteStream(path, options) { else if (typeof options === 'string') options = { encoding: options }; else if (options === null || typeof options !== 'object') - throw new TypeError('"options" argument must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); options = Object.create(options); @@ -1803,10 +1802,10 @@ function WriteStream(path, options) { if (this.start !== undefined) { if (typeof this.start !== 'number') { - throw new TypeError('"start" option must be a Number'); + throw new errors.TypeError('INVALIDOPT', 'start', 'number'); } if (this.start < 0) { - throw new Error('"start" must be >= zero'); + throw new errors.Error('INVALIDOPT', 'start', 'number >= zero'); } this.pos = this.start; @@ -1996,7 +1995,7 @@ SyncWriteStream.prototype.destroySoon = SyncWriteStream.prototype.destroy; fs.mkdtemp = function(prefix, options, callback) { if (!prefix || typeof prefix !== 'string') - throw new TypeError('filename prefix is required'); + throw new errors.TypeError('REQUIREDARG', 'prefix'); options = options || {}; if (typeof options === 'function') { @@ -2006,7 +2005,7 @@ fs.mkdtemp = function(prefix, options, callback) { options = {encoding: options}; } if (typeof options !== 'object') - throw new TypeError('"options" must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); if (!nullCheck(prefix, callback)) { return; @@ -2020,13 +2019,13 @@ fs.mkdtemp = function(prefix, options, callback) { fs.mkdtempSync = function(prefix, options) { if (!prefix || typeof prefix !== 'string') - throw new TypeError('filename prefix is required'); + throw new errors.TypeError('REQUIREDARG', 'prefix'); options = options || {}; if (typeof options === 'string') options = {encoding: options}; if (typeof options !== 'object') - throw new TypeError('"options" must be a string or an object'); + throw new errors.TypeError('INVALIDARG', 'options', ['Object', 'string']); nullCheck(prefix); return binding.mkdtemp(prefix + 'XXXXXX', options.encoding); diff --git a/lib/internal/bootstrap_node.js b/lib/internal/bootstrap_node.js index 7143ff2720d4cb..6931ca7b1e01f9 100644 --- a/lib/internal/bootstrap_node.js +++ b/lib/internal/bootstrap_node.js @@ -369,7 +369,13 @@ } if (!NativeModule.exists(id)) { - throw new Error(`No such native module ${id}`); + const err = new Error(`No such native module ${id}`); + // Cannot depend on internal/errors here, set manually. + // Note, these are not set as read only accessors whereas + // in internal/errors they are set to be read only. + err.name = 'Error[ENOMODULE]'; + err.code = err.errno = 'ENOMODULE'; + throw err; } process.moduleLoadList.push(`NativeModule ${id}`); diff --git a/lib/internal/child_process.js b/lib/internal/child_process.js index ff26e8b75ab58c..00f7423898a2d4 100644 --- a/lib/internal/child_process.js +++ b/lib/internal/child_process.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const StringDecoder = require('string_decoder').StringDecoder; const Buffer = require('buffer').Buffer; const EventEmitter = require('events'); @@ -361,7 +362,7 @@ ChildProcess.prototype.kill = function(sig) { } if (signal === undefined) { - throw new Error('Unknown signal: ' + sig); + throw new errors.Error('UNKNOWNSIGNAL', sig); } if (this._handle) { @@ -512,7 +513,7 @@ function setupChannel(target, channel) { options = undefined; } else if (options !== undefined && (options === null || typeof options !== 'object')) { - throw new TypeError('"options" argument must be an object'); + throw new errors.TypeError('INVALIDARG', 'options', 'object'); } options = Object.assign({swallowErrors: false}, options); @@ -520,7 +521,7 @@ function setupChannel(target, channel) { if (this.connected) { return this._send(message, handle, options, callback); } - const ex = new Error('channel closed'); + const ex = new errors.Error('CHANNELCLOSED'); if (typeof callback === 'function') { process.nextTick(callback, ex); } else { @@ -533,7 +534,7 @@ function setupChannel(target, channel) { assert(this.connected || this._channel); if (message === undefined) - throw new TypeError('"message" argument cannot be undefined'); + throw new errors.TypeError('REQUIREDARG', 'message'); // Support legacy function signature if (typeof options === 'boolean') { @@ -560,7 +561,7 @@ function setupChannel(target, channel) { } else if (handle instanceof UDP) { message.type = 'dgram.Native'; } else { - throw new TypeError('This handle type can\'t be sent'); + throw new errors.TypeError('IPCBADHANDLE'); } // Queue-up message and handle if we haven't received ACK yet. @@ -657,7 +658,7 @@ function setupChannel(target, channel) { target.disconnect = function() { if (!this.connected) { - this.emit('error', new Error('IPC channel is already disconnected')); + this.emit('error', new errors.Error('IPCDISCONNECTED')); return; } @@ -730,11 +731,12 @@ function _validateStdio(stdio, sync) { case 'ignore': stdio = ['ignore', 'ignore', 'ignore']; break; case 'pipe': stdio = ['pipe', 'pipe', 'pipe']; break; case 'inherit': stdio = [0, 1, 2]; break; - default: throw new TypeError('Incorrect value of stdio option: ' + stdio); + default: throw new errors.TypeError('INVALIDOPTVALUE', 'stdio', stdio); } } else if (!Array.isArray(stdio)) { - throw new TypeError('Incorrect value of stdio option: ' + - util.inspect(stdio)); + throw new errors.TypeError('INVALIDOPTVALUE', + 'stdio', + util.inspect(stdio)); } // At least 3 stdio will be created @@ -778,9 +780,9 @@ function _validateStdio(stdio, sync) { // Cleanup previously created pipes cleanup(); if (!sync) - throw new Error('Child process can have only one IPC pipe'); + throw new errors.Error('IPCONEIPC'); else - throw new Error('You cannot use IPC with synchronous forks'); + throw new errors.Error('IPCNOSYNCFORK'); } ipc = new Pipe(true); @@ -815,14 +817,14 @@ function _validateStdio(stdio, sync) { } else if (stdio instanceof Buffer || typeof stdio === 'string') { if (!sync) { cleanup(); - throw new TypeError('Asynchronous forks do not support Buffer input: ' + - util.inspect(stdio)); + throw new errors.TypeError('IPCBUFFERASYNCFORK', util.inspect(stdio)); } } else { // Cleanup cleanup(); - throw new TypeError('Incorrect value for stdio stream: ' + - util.inspect(stdio)); + throw new errors.TypeError('INVALIDOPTVALUE', + 'stdio', + util.inspect(stdio)); } return acc; diff --git a/lib/internal/errors.js b/lib/internal/errors.js new file mode 100644 index 00000000000000..8a2ce91c075eb2 --- /dev/null +++ b/lib/internal/errors.js @@ -0,0 +1,282 @@ +'use strict'; + +// The whole point behind this internal module is to allow Node.js to no +// longer be forced to treat every error message change as a semver-major +// change. The NodeError classes here all expose a `code` property whose +// value identifies the error. + +// Note: the lib/_stream_readable.js and lib/_stream_writable.js files +// include their own alternatives to this because those need to be portable +// to the readable-stream standalone module. If the logic here changes at all, +// then those also need to be checked. + +const kCode = Symbol('code'); + +class NodeError extends Error { + constructor(key, ...args) { + super(message(key, args)); + this[kCode] = key; + Error.captureStackTrace(this, NodeError); + } + + get name() { + return `Error[${this[kCode]}]`; + } + + get code() { + return this[kCode]; + } +} + +class NodeTypeError extends TypeError { + constructor(key, ...args) { + super(message(key, args)); + this[kCode] = key; + Error.captureStackTrace(this, NodeTypeError); + } + + get name() { + return `TypeError[${this[kCode]}]`; + } + + get code() { + return this[kCode]; + } +} + +class NodeRangeError extends RangeError { + constructor(key, ... args) { + super(message(key, args)); + this[kCode] = key; + Error.captureStackTrace(this, NodeRangeError); + } + + get name() { + return `RangeError[${this[kCode]}]`; + } + + get code() { + return this[kCode]; + } +} + +exports.Error = NodeError; +exports.TypeError = NodeTypeError; +exports.RangeError = NodeRangeError; + +var assert, util; +function lazyAssert() { + if (!assert) + assert = require('assert'); + return assert; +} + +function lazyUtil() { + if (!util) + util = require('util'); + return util; +} + +function message(key, args) { + const assert = lazyAssert(); + const util = lazyUtil(); + const msg = Error[Symbol.for(key)]; + // We want to assert here because if this happens, + // Node.js is doing something incorrect. + assert(msg, `An invalid error message key was used: ${key}.`); + let fmt = util.format; + if (typeof msg === 'function') { + fmt = msg; + } else { + args.unshift(msg); + } + const res = fmt.apply(null, args); + // We want to assert here because if this happens, + // Node.js is doing something incorrect. + assert(res, 'An invalid formatted error message was returned.'); + return res; +} + +// Below is the catalog of messages +// Error keys should be all caps, value can +// be a string formatted for use with +// util.format, or a function. + +// Utility function for registering the error codes. +function E(sym, val) { + Error[Symbol.for(sym)] = val; +} + +E('ARGNOTNUM', argNotNum); +E('ASSERTIONERROR', (msg) => msg); +E('BUFFERLIST', + '"buffer" list argument must contain only Buffers or strings'); +E('BUFFERSWAP16', 'Buffer size must be a multiple of 16-bits'); +E('BUFFERSWAP32', 'Buffer size must be a multiple of 32-bits'); +E('BUFFERTOOLARGE', bufferTooBig); +E('CALLBACKREQUIRED', 'The required "callback" argument is not a function'); +E('CHANNELCLOSED', 'Channel closed'); +E('CLOSEDBEFOREREPLY', 'Slave closed before reply'); +E('CONSOLEWRITABLE', 'Console expects a writable stream instance'); +E('DNSUNKNOWNDOMAIN', 'Unable to determine the domain name'); +E('ECONNRESET', 'Socket hang up'); +E('FILETOOBIG', fileTooBig); +E('HEADERSSENT', + 'Unable to set additional headers after they are already sent'); +E('HTTPINVALIDHEADER', 'The header content contains invalid characters'); +E('HTTPINVALIDPATH', 'Request path contains unescaped characters'); +E('HTTPINVALIDSTATUS', (status) => `Invalid status code: ${status}`); +E('HTTPINVALIDTOKEN', (val) => `Invalid HTTP token: ${val}`); +E('HTTPINVALIDTRAILER', 'The trailer content contains invalid characters'); +E('HTTPMETHODINVALID', 'Method must be a valid HTTP token'); +E('HTTPUNSUPPORTEDPROTOCOL', unsupportedProtocol); +E('INDEXOUTOFRANGE', 'Index out of range'); +E('INVALIDARG', invalidArgument); +E('INVALIDARGVALUE', invalidArgumentValue); +E('INVALIDFAMILY', 'IP protocol family must be either 4 or 6'); +E('INVALIDOPT', invalidOption); +E('INVALIDOPTVALUE', invalidOptionValue); +E('INVALIDPID', 'Invalid pid'); +E('IPCBADHANDLE', 'Handles of this type cannot be sent'); +E('IPCBUFFERASYNCFORK', + (stdio) => `Asynchronous forks do not support Buffer input: ${stdio}`); +E('IPCDISCONNECTED', 'The IPC channel is already disconnected'); +E('IPCNOSYNCFORK', 'IPC cannot be used with synchronous forks'); +E('IPCONEIPC', 'Child process can have only one IPC pipe'); +E('NOCPUUSAGE', (err) => `Unable to obtain CPU Usage: ${err}`); +E('NOCRYPTO', 'Node.js is not compiled with openssl crypto support'); +E('NOTIMPLEMENTED', notImplemented); +E('OUTOFBOUNDSARG', (arg) => `"${arg}" argument is out of bounds`); +E('PORTRANGE', '"port" argument must be a number >= 0 and < 65536'); +E('PROTOTYPEREQUIRED', + 'The super constructor to "inherits" must have a prototype'); +E('REPLHISTORYPARSE', (path) => `Could not parse history data in ${path}`); +E('REQUIREDARG', requiredArgument); +E('STDERRCLOSE', 'process.stderr cannot be closed'); +E('STDOUTCLOSE', 'process.stdout cannot be closed'); +E('UNKNOWNENCODING', (enc) => `Unknown encoding: ${enc}`); +E('UNKNOWNSIGNAL', (sig) => `Unknown signal: ${sig}`); +E('UNKNOWNSTATE', 'Unknown state'); +E('WRITEOUTOFBOUNDS', 'Write out of bounds'); + +// Utility methods for the messages above + +function bufferTooBig(max) { + const assert = lazyAssert(); + assert(max !== undefined); + return 'Cannot create final Buffer. It would be larger ' + + `than 0x${max.toString(16)} bytes`; +} + +function fileTooBig(arg) { + const assert = lazyAssert(); + assert(arg !== undefined); + return 'File size is greater than possible Buffer: ' + + `0x${arg.toString(16)} bytes`; +} + +function unsupportedProtocol(protocol, expected) { + let msg = `Protocol "${protocol}" is not supported`; + if (expected) { + msg += `. Expected "${expected}"`; + } + return msg; +} + +function notImplemented(additional) { + let msg = 'Not implemented'; + if (additional) + msg += `: ${additional}`; + return msg; +} + +function invalidOptionValue(opt, val) { + const assert = lazyAssert(); + assert(opt); + let msg = `Invalid value for "${opt}" option`; + if (val) + msg += `: ${val}`; + return msg; +} + +function invalidArgumentValue(arg, val) { + const assert = lazyAssert(); + assert(arg); + let msg = `Invalid value for "${arg}" argument`; + if (val) + msg += `: ${val}`; + return msg; +} + +function argNotNum(arg) { + const assert = lazyAssert(); + assert(arg); + return `"${arg}" argument must not be a number`; +} + +function requiredArgument(arg) { + const assert = lazyAssert(); + assert(arg); + return `"${arg}" argument is required and cannot be undefined`; +} + +function invalidArgument(arg, expected) { + return invalidOptionOrArgument(true, arg, expected); +} + +function invalidOption(opt, expected) { + return invalidOptionOrArgument(false, opt, expected); +} + +function formatArg(arg) { + if (typeof arg === 'number') { + arg >>>= 0; + switch (arg) { + case 1: + arg = 'The first'; + break; + case 2: + arg = 'The second'; + break; + case 3: + arg = 'The third'; + break; + default: + arg = `The ${arg}th`; + } + } else { + arg = `"${arg}"`; + } + return arg; +} + +// This takes an arg name and one or more expected types to generate an +// appropriate error message indicating that the given argument must be +// of the expected type. For instance: +// "foo" argument must be a string +// "bar" argument must be an object +// "baz" argument must be one of: string, object +// first argument must be a string +// ... and so forth. +function invalidOptionOrArgument(isargument, arg, expected) { + const assert = lazyAssert(); + const util = lazyUtil(); + assert(arg && expected); + // Is this error message for an argument or an option? + const what = isargument ? 'argument' : 'option'; + if (typeof expected === 'string') { + // Check to see if we need "a" or "an". + const ch = expected.codePointAt(0) | 0x20; + const prefix = (ch === 0x61 || ch === 0x65 || + ch === 0x69 || ch === 0x6f || + ch === 0x75) ? 'an' : 'a'; + return `${formatArg(arg)} ${what} must be ${prefix} ${expected}`; + } else if (Array.isArray(expected)) { + // Is there only a single item in the array or multiple? + return expected.length === 1 ? + invalidArgument(arg, expected[0]) : + `${formatArg(arg)} ${what} must be one of: ${expected.join(', ')}`; + } else { + assert.fail(null, null, `Unexpected value: ${util.inspect(expected)}`); + } +} diff --git a/lib/internal/net.js b/lib/internal/net.js index d19bc4c219a796..9d93ca92d67575 100644 --- a/lib/internal/net.js +++ b/lib/internal/net.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); module.exports = { isLegalPort, assertPort }; // Check that the port number is not NaN when coerced to a number, @@ -14,5 +15,5 @@ function isLegalPort(port) { function assertPort(port) { if (typeof port !== 'undefined' && !isLegalPort(port)) - throw new RangeError('"port" argument must be >= 0 and < 65536'); + throw new errors.RangeError('PORTRANGE'); } diff --git a/lib/internal/process.js b/lib/internal/process.js index c435c2e8712a03..a97b5e681c0bbc 100644 --- a/lib/internal/process.js +++ b/lib/internal/process.js @@ -9,6 +9,13 @@ function lazyConstants() { return _lazyConstants; } +var _errors = null; +function lazyErrors() { + if (!_errors) + _errors = require('internal/errors'); + return _errors; +} + exports.setup_cpuUsage = setup_cpuUsage; exports.setup_hrtime = setup_hrtime; exports.setupConfig = setupConfig; @@ -19,7 +26,10 @@ exports.setupRawDebug = setupRawDebug; const assert = process.assert = function(x, msg) { - if (!x) throw new Error(msg || 'assertion error'); + if (!x) { + const errors = lazyErrors(); + throw new errors.Error('ASSERTIONERROR', msg || 'assertion error'); + } }; @@ -37,18 +47,21 @@ function setup_cpuUsage() { // If a previous value was passed in, ensure it has the correct shape. if (prevValue) { if (!previousValueIsValid(prevValue.user)) { - throw new TypeError('value of user property of argument is invalid'); + const errors = lazyErrors(); + throw errors.TypeError('INVALIDARGVALUE', 'prevValue.user'); } if (!previousValueIsValid(prevValue.system)) { - throw new TypeError('value of system property of argument is invalid'); + const errors = lazyErrors(); + throw errors.TypeError('INVALIDARGVALUE', 'prevValue.system'); } } // Call the native function to get the current values. const errmsg = _cpuUsage(cpuValues); if (errmsg) { - throw new Error('unable to obtain CPU usage: ' + errmsg); + const errors = lazyErrors(); + throw errors.Error('NOCPUUSAGE', errmsg); } // If a previous value was passed in, return diff of current from previous. @@ -88,7 +101,8 @@ function setup_hrtime() { return [nsec < 0 ? sec - 1 : sec, nsec < 0 ? nsec + 1e9 : nsec]; } - throw new TypeError('process.hrtime() only accepts an Array tuple'); + const errors = lazyErrors(); + throw errors.TypeError('INVALIDARG', 'prevValue', 'Array'); } return [ @@ -152,7 +166,8 @@ function setupKillAndExit() { var err; if (pid != (pid | 0)) { - throw new TypeError('invalid pid'); + const errors = lazyErrors(); + throw errors.TypeError('INVALIDPID'); } // preserve null signal @@ -164,7 +179,8 @@ function setupKillAndExit() { sig.slice(0, 3) === 'SIG') { err = process._kill(pid, lazyConstants()[sig]); } else { - throw new Error(`Unknown signal: ${sig}`); + const errors = lazyErrors(); + throw errors.Error('UNKNOWNSIGNAL', sig); } } diff --git a/lib/internal/process/next_tick.js b/lib/internal/process/next_tick.js index 529645aa8d65c4..4c8a17489a725a 100644 --- a/lib/internal/process/next_tick.js +++ b/lib/internal/process/next_tick.js @@ -138,8 +138,10 @@ function setupNextTick() { } function nextTick(callback) { - if (typeof callback !== 'function') - throw new TypeError('callback is not a function'); + if (typeof callback !== 'function') { + const errors = require('internal/errors'); + throw new errors.TypeError('CALLBACKREQUIRED'); + } // on the way out, don't bother. it won't get fired anyway. if (process._exiting) return; diff --git a/lib/internal/process/stdio.js b/lib/internal/process/stdio.js index 55689069ff2cb5..9159288dc1a349 100644 --- a/lib/internal/process/stdio.js +++ b/lib/internal/process/stdio.js @@ -2,6 +2,13 @@ exports.setup = setupStdio; +var _errors = null; +function lazyErrors() { + if (!_errors) + _errors = require('internal/errors'); + return _errors; +} + function setupStdio() { var stdin, stdout, stderr; @@ -9,7 +16,8 @@ function setupStdio() { if (stdout) return stdout; stdout = createWritableStdioStream(1); stdout.destroy = stdout.destroySoon = function(er) { - er = er || new Error('process.stdout cannot be closed.'); + const errors = lazyErrors(); + er = er || new errors.Error('STDOUTCLOSE'); stdout.emit('error', er); }; if (stdout.isTTY) { @@ -24,7 +32,8 @@ function setupStdio() { if (stderr) return stderr; stderr = createWritableStdioStream(2); stderr.destroy = stderr.destroySoon = function(er) { - er = er || new Error('process.stderr cannot be closed.'); + const errors = lazyErrors(); + er = er || new errors.Error('STDERRCLOSE'); stderr.emit('error', er); }; if (stderr.isTTY) { @@ -83,7 +92,8 @@ function setupStdio() { default: // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stdin file type!'); + const errors = lazyErrors(); + throw new errors.Error('NOTIMPLEMENTED', 'Unknown stdin file type'); } // For supporting legacy API we put the FD here. @@ -149,7 +159,8 @@ function createWritableStdioStream(fd) { default: // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stream file type!'); + const errors = lazyErrors(); + throw new errors.Error('NOTIMPLEMENTED', 'Unknown stream file type'); } // For supporting legacy API we put the FD here. diff --git a/lib/internal/process/warning.js b/lib/internal/process/warning.js index 4087eb0d2847e7..61e6b8b960e84e 100644 --- a/lib/internal/process/warning.js +++ b/lib/internal/process/warning.js @@ -39,7 +39,8 @@ function setupProcessWarnings() { Error.captureStackTrace(warning, ctor || process.emitWarning); } if (!(warning instanceof Error)) { - throw new TypeError('\'warning\' must be an Error object or string.'); + const errors = require('internal/errors'); + throw new errors.TypeError('INVALIDARG', 'warning', ['Error', 'string']); } if (throwDeprecation && warning.name === 'DeprecationWarning') throw warning; diff --git a/lib/internal/repl.js b/lib/internal/repl.js index cea681f5837494..cc6eb25108f955 100644 --- a/lib/internal/repl.js +++ b/lib/internal/repl.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const Interface = require('readline').Interface; const REPL = require('repl'); const path = require('path'); @@ -152,13 +153,14 @@ function setupHistory(repl, historyPath, oldHistoryPath, ready) { if (oldReplJSONHistory) repl.history = JSON.parse(oldReplJSONHistory); if (!Array.isArray(repl.history)) { - throw new Error('Expected array, got ' + typeof repl.history); + throw new errors.Error('INVALIDARGVALUE', + 'repl.history', + typeof repl.history); } repl.history = repl.history.slice(0, repl.historySize); } catch (err) { if (err.code !== 'ENOENT') { - return ready( - new Error(`Could not parse history data in ${oldHistoryPath}.`)); + return ready(new errors.Error('REPLHISTORYPARSE', oldHistoryPath)); } } } diff --git a/lib/internal/socket_list.js b/lib/internal/socket_list.js index 0f4be6df239dd6..cf4f648e180c23 100644 --- a/lib/internal/socket_list.js +++ b/lib/internal/socket_list.js @@ -2,6 +2,7 @@ module.exports = {SocketListSend, SocketListReceive}; +const errors = require('internal/errors'); const EventEmitter = require('events'); const util = require('util'); @@ -22,7 +23,7 @@ SocketListSend.prototype._request = function(msg, cmd, callback) { function onclose() { self.slave.removeListener('internalMessage', onreply); - callback(new Error('Slave closed before reply')); + callback(new errors.Error('CLOSEDBEFOREREPLY')); } function onreply(msg) { diff --git a/lib/internal/util.js b/lib/internal/util.js index 56398ccf9dc2fe..70ad6808be33fe 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const binding = process.binding('util'); const prefix = `(${process.release.name}:${process.pid}) `; const noDeprecation = process.noDeprecation; @@ -88,5 +89,5 @@ exports.objectToString = function objectToString(o) { const noCrypto = !process.versions.openssl; exports.assertCrypto = function(exports) { if (noCrypto) - throw new Error('Node.js is not compiled with openssl crypto support'); + throw new errors.Error('NOCRYPTO'); }; diff --git a/lib/net.js b/lib/net.js index 509112f58f2077..e0ce8846cd28fb 100644 --- a/lib/net.js +++ b/lib/net.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const EventEmitter = require('events'); const stream = require('stream'); const timers = require('timers'); @@ -32,7 +33,7 @@ function createHandle(fd) { var type = TTYWrap.guessHandleType(fd); if (type === 'PIPE') return new Pipe(); if (type === 'TCP') return new TCP(); - throw new TypeError('Unsupported fd type: ' + type); + throw new errors.TypeError('INVALIDARGVALUE', 'fd', 'type'); } @@ -644,8 +645,7 @@ protoGetter('localPort', function localPort() { Socket.prototype.write = function(chunk, encoding, cb) { if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) { - throw new TypeError( - 'Invalid data, chunk must be a string or buffer, not ' + typeof chunk); + throw new errors.TypeError('INVALIDARG', 'chunk', ['Buffer', 'string']); } return stream.Duplex.prototype.write.apply(this, arguments); }; @@ -824,7 +824,9 @@ function connect(self, address, port, addressType, localAddress, localPort) { localAddress = localAddress || '::'; bind = self._handle.bind6; } else { - self._destroy(new TypeError('Invalid addressType: ' + addressType)); + self._destroy(new errors.TypeError('INVALIDARGVALUE', + 'addressType', + addressType)); return; } @@ -939,19 +941,20 @@ function lookupAndConnect(self, options) { var localAddress = options.localAddress; var localPort = options.localPort; - if (localAddress && !exports.isIP(localAddress)) - throw new TypeError('"localAddress" option must be a valid IP: ' + - localAddress); + if (localAddress && !exports.isIP(localAddress)) { + throw new errors.TypeError('INVALIDOPT', + 'localAddress', + 'valid IP address'); + } if (localPort && typeof localPort !== 'number') - throw new TypeError('"localPort" option should be a number: ' + localPort); + throw new errors.TypeError('INVALIDOPT', 'localPort', 'number'); if (typeof port !== 'undefined') { if (typeof port !== 'number' && typeof port !== 'string') - throw new TypeError('"port" option should be a number or string: ' + - port); + throw new errors.TypeError('INVALIDOPT', 'port', ['string', 'number']); if (!isLegalPort(port)) - throw new RangeError('"port" option should be >= 0 and < 65536: ' + port); + throw new errors.RangeError('PORTRANGE'); } port |= 0; @@ -966,7 +969,7 @@ function lookupAndConnect(self, options) { } if (options.lookup && typeof options.lookup !== 'function') - throw new TypeError('"lookup" option should be a function'); + throw new errors.TypeError('INVALIDOPT', 'lookup', 'function'); var dnsopts = { family: options.family, @@ -1108,7 +1111,7 @@ function Server(options, connectionListener) { this.on('connection', connectionListener); } } else { - throw new TypeError('options must be an object'); + throw new errors.TypeError('INVALIDARG', 'options', 'object'); } this._connections = 0; diff --git a/lib/path.js b/lib/path.js index cad86ab9a21731..1f35c98e5cbfb7 100644 --- a/lib/path.js +++ b/lib/path.js @@ -1,10 +1,10 @@ 'use strict'; -const inspect = require('util').inspect; +const errors = require('internal/errors'); function assertPath(path) { if (typeof path !== 'string') { - throw new TypeError('Path must be a string. Received ' + inspect(path)); + throw new errors.TypeError('INVALIDARG', 'path', 'string'); } } @@ -797,7 +797,7 @@ const win32 = { basename: function basename(path, ext) { if (ext !== undefined && typeof ext !== 'string') - throw new TypeError('"ext" argument must be a string'); + throw new errors.TypeError('INVALIDARG', 'ext', 'string'); assertPath(path); var start = 0; var end = -1; @@ -938,9 +938,7 @@ const win32 = { format: function format(pathObject) { if (pathObject === null || typeof pathObject !== 'object') { - throw new TypeError( - `Parameter "pathObject" must be an object, not ${typeof pathObject}` - ); + throw new errors.TypeError('INVALIDARG', 'pathObject', 'object'); } return _format('\\', pathObject); }, @@ -1351,7 +1349,7 @@ const posix = { basename: function basename(path, ext) { if (ext !== undefined && typeof ext !== 'string') - throw new TypeError('"ext" argument must be a string'); + throw new errors.TypeError('INVALIDARG', 'ext', 'string'); assertPath(path); var start = 0; @@ -1480,9 +1478,7 @@ const posix = { format: function format(pathObject) { if (pathObject === null || typeof pathObject !== 'object') { - throw new TypeError( - `Parameter "pathObject" must be an object, not ${typeof pathObject}` - ); + throw new errors.TypeError('INVALIDARG', 'pathObject', 'object'); } return _format('/', pathObject); }, diff --git a/lib/readline.js b/lib/readline.js index 57a27a97705e73..ab7635cb4d0d5f 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -8,6 +8,7 @@ const kHistorySize = 30; +const errors = require('internal/errors'); const util = require('util'); const debug = util.debuglog('readline'); const inherits = util.inherits; @@ -54,7 +55,7 @@ function Interface(input, output, completer, terminal) { } if (completer && typeof completer !== 'function') { - throw new TypeError('Argument "completer" must be a function'); + throw new errors.TypeError('INVALIDARG', 'completer', 'function'); } if (historySize === undefined) { @@ -64,7 +65,7 @@ function Interface(input, output, completer, terminal) { if (typeof historySize !== 'number' || isNaN(historySize) || historySize < 0) { - throw new TypeError('Argument "historySize" must be a positive number'); + throw new errors.TypeError('INVALIDARG', 'historySize', 'positive number'); } // backwards compat; check the isTTY prop of the output stream @@ -221,7 +222,7 @@ Interface.prototype._onLine = function(line) { Interface.prototype._writeToOutput = function _writeToOutput(stringToWrite) { if (typeof stringToWrite !== 'string') - throw new TypeError('"stringToWrite" argument must be a string'); + throw new errors.TypeError('INVALIDARG', 'stringToWrite', 'string'); if (this.output !== null && this.output !== undefined) this.output.write(stringToWrite); diff --git a/lib/timers.js b/lib/timers.js index dc2506e01e09a4..7085f0c161616b 100644 --- a/lib/timers.js +++ b/lib/timers.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const TimerWrap = process.binding('timer_wrap').Timer; const L = require('internal/linkedlist'); const assert = require('assert'); @@ -282,12 +283,13 @@ const unenroll = exports.unenroll = function(item) { // Using existing objects as timers slightly reduces object overhead. exports.enroll = function(item, msecs) { if (typeof msecs !== 'number') { - throw new TypeError('"msecs" argument must be a number'); + throw new errors.TypeError('INVALIDARG', 'msecs', 'number'); } if (msecs < 0 || !isFinite(msecs)) { - throw new RangeError('"msecs" argument must be ' + - 'a non-negative finite number'); + throw new errors.RangeError('INVALIDARG', + 'msecs', + 'non-negative finite number'); } // if this item was already in a list somewhere @@ -311,7 +313,7 @@ exports.enroll = function(item, msecs) { exports.setTimeout = function(callback, after) { if (typeof callback !== 'function') { - throw new TypeError('"callback" argument must be a function'); + throw new errors.TypeError('CALLBACKREQUIRED'); } after *= 1; // coalesce to number or NaN @@ -371,7 +373,7 @@ const clearTimeout = exports.clearTimeout = function(timer) { exports.setInterval = function(callback, repeat) { if (typeof callback !== 'function') { - throw new TypeError('"callback" argument must be a function'); + throw new errors.TypeError('CALLBACKREQUIRED'); } repeat *= 1; // coalesce to number or NaN @@ -565,7 +567,7 @@ Immediate.prototype._idlePrev = undefined; exports.setImmediate = function(callback, arg1, arg2, arg3) { if (typeof callback !== 'function') { - throw new TypeError('"callback" argument must be a function'); + throw new errors.TypeError('CALLBACKREQUIRED'); } var i, args; diff --git a/lib/url.js b/lib/url.js index c4d6ed2e33a5f5..79142457ffe50c 100644 --- a/lib/url.js +++ b/lib/url.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const punycode = require('punycode'); exports.parse = urlParse; @@ -76,7 +77,7 @@ function urlParse(url, parseQueryString, slashesDenoteHost) { Url.prototype.parse = function(url, parseQueryString, slashesDenoteHost) { if (typeof url !== 'string') { - throw new TypeError('Parameter "url" must be a string, not ' + typeof url); + throw new errors.TypeError('INVALIDARG', 'url', 'string'); } // Copy chrome, IE, opera backslash-handling behavior. @@ -532,13 +533,12 @@ function urlFormat(obj) { // If it's an obj, this is a no-op. // this way, you can call url_format() on strings // to clean up potentially wonky urls. - if (typeof obj === 'string') obj = urlParse(obj); - + if (typeof obj === 'string') + obj = urlParse(obj); else if (typeof obj !== 'object' || obj === null) - throw new TypeError('Parameter "urlObj" must be an object, not ' + - obj === null ? 'null' : typeof obj); - - else if (!(obj instanceof Url)) return Url.prototype.format.call(obj); + throw new errors.TypeError('INVALIDARG', 'urlObj', 'object'); + else if (!(obj instanceof Url)) + return Url.prototype.format.call(obj); return obj.format(); } diff --git a/lib/util.js b/lib/util.js index 6fd399f88fd4d6..72e376410acfb0 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,5 +1,6 @@ 'use strict'; +const errors = require('internal/errors'); const uv = process.binding('uv'); const Buffer = require('buffer').Buffer; const internalUtil = require('internal/util'); @@ -881,16 +882,13 @@ exports.log = function() { exports.inherits = function(ctor, superCtor) { if (ctor === undefined || ctor === null) - throw new TypeError('The constructor to "inherits" must not be ' + - 'null or undefined'); + throw new errors.TypeError('REQUIREDARG', 'constructor'); if (superCtor === undefined || superCtor === null) - throw new TypeError('The super constructor to "inherits" must not ' + - 'be null or undefined'); + throw new errors.TypeError('REQUIREDARG', 'superConstructor'); if (superCtor.prototype === undefined) - throw new TypeError('The super constructor to "inherits" must ' + - 'have a prototype'); + throw new errors.TypeError('PROTOTYPEREQUIRED'); ctor.super_ = superCtor; Object.setPrototypeOf(ctor.prototype, superCtor.prototype); diff --git a/lib/zlib.js b/lib/zlib.js index 3915e5ddabd318..83f9a3599b4f6b 100644 --- a/lib/zlib.js +++ b/lib/zlib.js @@ -1,13 +1,12 @@ 'use strict'; +const errors = require('internal/errors'); const Buffer = require('buffer').Buffer; const Transform = require('_stream_transform'); const binding = process.binding('zlib'); const util = require('util'); const assert = require('assert').ok; const kMaxLength = require('buffer').kMaxLength; -const kRangeErrorMessage = 'Cannot create final Buffer. It would be larger ' + - 'than 0x' + kMaxLength.toString(16) + ' bytes'; // zlib doesn't provide these, so kludge them in following the same // const naming scheme zlib uses. @@ -217,7 +216,7 @@ function zlibBuffer(engine, buffer, callback) { var err = null; if (nread >= kMaxLength) { - err = new RangeError(kRangeErrorMessage); + err = new errors.RangeError('BUFFERTOOLARGE', kMaxLength); } else { buf = Buffer.concat(buffers, nread); } @@ -232,7 +231,7 @@ function zlibBufferSync(engine, buffer) { if (typeof buffer === 'string') buffer = Buffer.from(buffer); if (!(buffer instanceof Buffer)) - throw new TypeError('Not a string or buffer'); + throw new errors.TypeError('INVALIDARG', 'buffer', ['Buffer', 'string']); var flushFlag = engine._finishFlushFlag; @@ -399,14 +398,14 @@ util.inherits(Zlib, Transform); Zlib.prototype.params = function(level, strategy, callback) { if (level < exports.Z_MIN_LEVEL || level > exports.Z_MAX_LEVEL) { - throw new RangeError('Invalid compression level: ' + level); + throw new errors.RangeError('INVALIDARGVALUE', 'level', level); } if (strategy != exports.Z_FILTERED && strategy != exports.Z_HUFFMAN_ONLY && strategy != exports.Z_RLE && strategy != exports.Z_FIXED && strategy != exports.Z_DEFAULT_STRATEGY) { - throw new TypeError('Invalid strategy: ' + strategy); + throw new errors.TypeError('INVALIDARGVALUE', 'strategy', strategy); } if (this._level !== level || this._strategy !== strategy) { @@ -550,7 +549,7 @@ Zlib.prototype._processChunk = function(chunk, flushFlag, cb) { if (nread >= kMaxLength) { _close(this); - throw new RangeError(kRangeErrorMessage); + throw new errors.RangeError('BUFFERTOOLARGE', kMaxLength); } var buf = Buffer.concat(buffers, nread); diff --git a/node.gyp b/node.gyp index 86dffef5c6716c..fbffbb8c9db9db 100644 --- a/node.gyp +++ b/node.gyp @@ -72,6 +72,7 @@ 'lib/zlib.js', 'lib/internal/child_process.js', 'lib/internal/cluster.js', + 'lib/internal/errors.js', 'lib/internal/freelist.js', 'lib/internal/linkedlist.js', 'lib/internal/net.js', diff --git a/test/common.js b/test/common.js index ffd1ba1366f15f..c99ae44d2419c0 100644 --- a/test/common.js +++ b/test/common.js @@ -516,3 +516,26 @@ exports.nodeProcessAborted = function nodeProcessAborted(exitCode, signal) { return expectedExitCodes.indexOf(exitCode) > -1; } }; + +// Basically a wrapper for assert.throws that also understands Node.js +// custom error codes. If the second argument is an object with a `code` +// property on it, then that will be checked. +exports.throws = function throws(...args) { + if (args.length > 1 && + typeof args[1] === 'object' && + (typeof args[1].code === 'string' || + typeof args[1].name === 'string' || + typeof args[1].type === 'function')) { + assert.throws(args[0], (err) => { + const check = args[1]; + if ((typeof check.type === 'function' && !(err instanceof check.type)) || + (typeof check.name === 'string' && check.name !== err.name) || + (typeof check.code === 'string' && check.code !== err.code)) { + return false; + } + return true; + }); + } else { + assert.throws.apply(null, args); + } +}; diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index d06f7b678e714b..85de11ff14bdb9 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -481,8 +481,8 @@ function testBlockTypeError(method, block) { method(block); threw = false; } catch (e) { - assert.equal(e.toString(), - 'TypeError: "block" argument must be a function'); + assert.strictEqual(e.code, 'INVALIDARG'); + assert.ok(e instanceof TypeError); } assert.ok(threw); diff --git a/test/parallel/test-buffer-alloc.js b/test/parallel/test-buffer-alloc.js index 3ce8c71987a179..d30f5e1d64d55c 100644 --- a/test/parallel/test-buffer-alloc.js +++ b/test/parallel/test-buffer-alloc.js @@ -1436,16 +1436,13 @@ assert.throws(function() { Buffer.allocUnsafe(10).copy(); }); -const regErrorMsg = new RegExp('First argument must be a string, Buffer, ' + - 'ArrayBuffer, Array, or array-like object.'); - -assert.throws(function() { +common.throws(function() { Buffer.from(); -}, regErrorMsg); +}, {code: 'INVALIDARG'}); -assert.throws(function() { +common.throws(function() { Buffer.from(null); -}, regErrorMsg); +}, {code: 'INVALIDARG'}); // Test that ParseArrayIndex handles full uint32 diff --git a/test/parallel/test-buffer-compare-offset.js b/test/parallel/test-buffer-compare-offset.js index 540d644c2847ff..c2362ff14d5449 100644 --- a/test/parallel/test-buffer-compare-offset.js +++ b/test/parallel/test-buffer-compare-offset.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const a = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]); @@ -52,12 +52,12 @@ assert.equal(1, a.compare(b, Infinity, -Infinity)); // zero length target because default for targetEnd <= targetSource assert.equal(1, a.compare(b, '0xff')); -const oor = /out of range index/; +const oor = {code: 'INDEXOUTOFRANGE'}; -assert.throws(() => a.compare(b, 0, 100, 0), oor); -assert.throws(() => a.compare(b, 0, 1, 0, 100), oor); -assert.throws(() => a.compare(b, -1), oor); -assert.throws(() => a.compare(b, 0, '0xff'), oor); -assert.throws(() => a.compare(b, 0, Infinity), oor); -assert.throws(() => a.compare(b, -Infinity, Infinity), oor); -assert.throws(() => a.compare(), /Argument must be a Buffer/); +common.throws(() => a.compare(b, 0, 100, 0), oor); +common.throws(() => a.compare(b, 0, 1, 0, 100), oor); +common.throws(() => a.compare(b, -1), oor); +common.throws(() => a.compare(b, 0, '0xff'), oor); +common.throws(() => a.compare(b, 0, Infinity), oor); +common.throws(() => a.compare(b, -Infinity, Infinity), oor); +common.throws(() => a.compare(), {code: 'INVALIDARG'}); diff --git a/test/parallel/test-buffer-concat.js b/test/parallel/test-buffer-concat.js index bc628e656b4f11..5d38d9bd8e5048 100644 --- a/test/parallel/test-buffer-concat.js +++ b/test/parallel/test-buffer-concat.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); -var assert = require('assert'); +const common = require('../common'); +const assert = require('assert'); var zero = []; var one = [ Buffer.from('asdf') ]; @@ -28,10 +28,7 @@ assertWrongList(['hello', 'world']); assertWrongList(['hello', Buffer.from('world')]); function assertWrongList(value) { - assert.throws(function() { + common.throws(function() { Buffer.concat(value); - }, function(err) { - return err instanceof TypeError && - err.message === '"list" argument must be an Array of Buffers'; - }); + }, {code: 'INVALIDARG', type: TypeError}); } diff --git a/test/parallel/test-buffer.js b/test/parallel/test-buffer.js index d9a9d4e3b0e9c0..9dbf740095dc8c 100644 --- a/test/parallel/test-buffer.js +++ b/test/parallel/test-buffer.js @@ -1439,16 +1439,13 @@ assert.throws(function() { Buffer(10).copy(); }); -const regErrorMsg = new RegExp('First argument must be a string, Buffer, ' + - 'ArrayBuffer, Array, or array-like object.'); - -assert.throws(function() { +common.throws(function() { new Buffer(); -}, regErrorMsg); +}, {code: 'INVALIDARG'}); -assert.throws(function() { +common.throws(function() { new Buffer(null); -}, regErrorMsg); +}, {code: 'INVALIDARG'}); // Test prototype getters don't throw diff --git a/test/parallel/test-child-process-spawn-typeerror.js b/test/parallel/test-child-process-spawn-typeerror.js index bf59779a75ff5d..a6e0d17e44de76 100644 --- a/test/parallel/test-child-process-spawn-typeerror.js +++ b/test/parallel/test-child-process-spawn-typeerror.js @@ -1,14 +1,14 @@ 'use strict'; +const common = require('../common'); const assert = require('assert'); const child_process = require('child_process'); const spawn = child_process.spawn; const fork = child_process.fork; const execFile = child_process.execFile; -const common = require('../common'); const cmd = common.isWindows ? 'rundll32' : 'ls'; const invalidcmd = 'hopefully_you_dont_have_this_on_your_machine'; -const invalidArgsMsg = /Incorrect value of args option/; -const invalidOptionsMsg = /"options" argument must be an object/; +const invalidArgsMsg = {code: 'INVALIDARG'}; +const invalidOptionsMsg = {code: 'INVALIDARG'}; const empty = common.fixturesDir + '/empty.js'; assert.throws(function() { @@ -38,19 +38,19 @@ assert.throws(function() { spawn(); }, /Bad argument/); -assert.throws(function() { +common.throws(function() { spawn(cmd, null); }, invalidArgsMsg); -assert.throws(function() { +common.throws(function() { spawn(cmd, true); }, invalidArgsMsg); -assert.throws(function() { +common.throws(function() { spawn(cmd, [], null); }, invalidOptionsMsg); -assert.throws(function() { +common.throws(function() { spawn(cmd, [], 1); }, invalidOptionsMsg); diff --git a/test/parallel/test-child-process-spawnsync-input.js b/test/parallel/test-child-process-spawnsync-input.js index fdd29aa7d0cc50..0d3036f1a15ce7 100644 --- a/test/parallel/test-child-process-spawnsync-input.js +++ b/test/parallel/test-child-process-spawnsync-input.js @@ -1,5 +1,5 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); @@ -55,9 +55,9 @@ var options = { input: 1234 }; -assert.throws(function() { +common.throws(function() { spawnSync('cat', [], options); -}, /TypeError:.*should be Buffer or string not number/); +}, {code: 'INVALIDOPT'}); options = { diff --git a/test/parallel/test-child-process-validate-stdio.js b/test/parallel/test-child-process-validate-stdio.js index 09d000467404b6..2f1dd17bbde440 100644 --- a/test/parallel/test-child-process-validate-stdio.js +++ b/test/parallel/test-child-process-validate-stdio.js @@ -1,19 +1,19 @@ 'use strict'; // Flags: --expose_internals -require('../common'); +const common = require('../common'); const assert = require('assert'); const _validateStdio = require('internal/child_process')._validateStdio; // should throw if string and not ignore, pipe, or inherit -assert.throws(function() { +common.throws(function() { _validateStdio('foo'); -}, /Incorrect value of stdio option/); +}, {code: 'INVALIDOPTVALUE'}); // should throw if not a string or array -assert.throws(function() { +common.throws(function() { _validateStdio(600); -}, /Incorrect value of stdio option/); +}, {code: 'INVALIDOPTVALUE'}); // should populate stdio with undefined if len < 3 { @@ -27,9 +27,9 @@ assert.throws(function() { // should throw if stdio has ipc and sync is true const stdio2 = ['ipc', 'ipc', 'ipc']; -assert.throws(function() { +common.throws(function() { _validateStdio(stdio2, true); -}, /You cannot use IPC with synchronous forks/); +}, {code: 'IPCNOSYNCFORK'}); { const stdio3 = [process.stdin, process.stdout, process.stderr]; diff --git a/test/parallel/test-console-instance.js b/test/parallel/test-console-instance.js index 2a8a6e3678f58a..63fd98a5b94b8d 100644 --- a/test/parallel/test-console-instance.js +++ b/test/parallel/test-console-instance.js @@ -1,5 +1,5 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const Stream = require('stream'); const Console = require('console').Console; @@ -19,16 +19,16 @@ assert.equal('function', typeof Console); // make sure that the Console constructor throws // when not given a writable stream instance -assert.throws(function() { +common.throws(function() { new Console(); -}, /Console expects a writable stream/); +}, {code: 'CONSOLEWRITABLE'}); // Console constructor should throw if stderr exists but is not writable -assert.throws(function() { +common.throws(function() { out.write = function() {}; err.write = undefined; new Console(out, err); -}, /Console expects writable stream instances/); +}, {code: 'CONSOLEWRITABLE'}); out.write = err.write = function(d) {}; diff --git a/test/parallel/test-dgram-setTTL.js b/test/parallel/test-dgram-setTTL.js index 88627a314c3848..e899080aff38d5 100644 --- a/test/parallel/test-dgram-setTTL.js +++ b/test/parallel/test-dgram-setTTL.js @@ -9,9 +9,9 @@ socket.on('listening', function() { var result = socket.setTTL(16); assert.strictEqual(result, 16); - assert.throws(function() { + common.throws(function() { socket.setTTL('foo'); - }, /Argument must be a number/); + }, {code: 'INVALIDARG'}); socket.close(); }); diff --git a/test/parallel/test-dns.js b/test/parallel/test-dns.js index cd5914e026d6a9..eab22a202c9c2b 100644 --- a/test/parallel/test-dns.js +++ b/test/parallel/test-dns.js @@ -1,5 +1,5 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const dns = require('dns'); @@ -150,9 +150,9 @@ assert.throws(function() { dns.lookupService('0.0.0.0'); }, /Invalid arguments/); -assert.throws(function() { +common.throws(function() { dns.lookupService('fasdfdsaf', 0, noop); -}, /"host" argument needs to be a valid IP address/); +}, {code: 'INVALIDARG'}); assert.doesNotThrow(function() { dns.lookupService('0.0.0.0', '0', noop); @@ -162,18 +162,18 @@ assert.doesNotThrow(function() { dns.lookupService('0.0.0.0', 0, noop); }); -assert.throws(function() { +common.throws(function() { dns.lookupService('0.0.0.0', null, noop); -}, /"port" should be >= 0 and < 65536, got "null"/); +}, {code: 'PORTRANGE'}); -assert.throws(function() { +common.throws(function() { dns.lookupService('0.0.0.0', undefined, noop); -}, /"port" should be >= 0 and < 65536, got "undefined"/); +}, {code: 'PORTRANGE'}); -assert.throws(function() { +common.throws(function() { dns.lookupService('0.0.0.0', 65538, noop); -}, /"port" should be >= 0 and < 65536, got "65538"/); +}, {code: 'PORTRANGE'}); -assert.throws(function() { +common.throws(function() { dns.lookupService('0.0.0.0', 'test', noop); -}, /"port" should be >= 0 and < 65536, got "test"/); +}, {code: 'PORTRANGE'}); diff --git a/test/parallel/test-file-write-stream3.js b/test/parallel/test-file-write-stream3.js index 1243460f9f314f..89cc802125a0b6 100644 --- a/test/parallel/test-file-write-stream3.js +++ b/test/parallel/test-file-write-stream3.js @@ -159,11 +159,11 @@ function run_test_3() { const run_test_4 = common.mustCall(function() { // Error: start must be >= zero - assert.throws( + common.throws( function() { fs.createWriteStream(filepath, { start: -5, flags: 'r+' }); }, - /"start" must be/ + {code: 'INVALIDOPT'} ); }); diff --git a/test/parallel/test-fs-access.js b/test/parallel/test-fs-access.js index 6b82b4bbf312c3..4a7cce69aa04c6 100644 --- a/test/parallel/test-fs-access.js +++ b/test/parallel/test-fs-access.js @@ -94,13 +94,13 @@ assert.throws(function() { fs.access(100, fs.F_OK, function(err) {}); }, /path must be a string or Buffer/); -assert.throws(function() { +common.throws(function() { fs.access(__filename, fs.F_OK); -}, /"callback" argument must be a function/); +}, {code: 'CALLBACKREQUIRED'}); -assert.throws(function() { +common.throws(function() { fs.access(__filename, fs.F_OK, {}); -}, /"callback" argument must be a function/); +}, {code: 'CALLBACKREQUIRED'}); assert.doesNotThrow(function() { fs.accessSync(__filename); diff --git a/test/parallel/test-fs-read-stream-inherit.js b/test/parallel/test-fs-read-stream-inherit.js index c4216f4e13905b..3160ee524d02c9 100644 --- a/test/parallel/test-fs-read-stream-inherit.js +++ b/test/parallel/test-fs-read-stream-inherit.js @@ -112,9 +112,9 @@ file6.on('end', function() { assert.equal(file6.data, 'yz\n'); }); -assert.throws(function() { +common.throws(function() { fs.createReadStream(rangeFile, Object.create({start: 10, end: 2})); -}, /"start" option must be <= "end" option/); +}, {code: 'INVALIDOPT'}); var stream = fs.createReadStream(rangeFile, Object.create({ start: 0, end: 0 })); diff --git a/test/parallel/test-fs-read-stream-throw-type-error.js b/test/parallel/test-fs-read-stream-throw-type-error.js index 81f924d355b91c..c8fae329fbe666 100644 --- a/test/parallel/test-fs-read-stream-throw-type-error.js +++ b/test/parallel/test-fs-read-stream-throw-type-error.js @@ -16,18 +16,18 @@ assert.doesNotThrow(function() { fs.createReadStream(example, {encoding: 'utf8'}); }); -assert.throws(function() { +common.throws(function() { fs.createReadStream(example, null); -}, /"options" argument must be a string or an object/); -assert.throws(function() { +}, {code: 'INVALIDARG'}); +common.throws(function() { fs.createReadStream(example, 123); -}, /"options" argument must be a string or an object/); -assert.throws(function() { +}, {code: 'INVALIDARG'}); +common.throws(function() { fs.createReadStream(example, 0); -}, /"options" argument must be a string or an object/); -assert.throws(function() { +}, {code: 'INVALIDARG'}); +common.throws(function() { fs.createReadStream(example, true); -}, /"options" argument must be a string or an object/); -assert.throws(function() { +}, {code: 'INVALIDARG'}); +common.throws(function() { fs.createReadStream(example, false); -}, /"options" argument must be a string or an object/); +}, {code: 'INVALIDARG'}); diff --git a/test/parallel/test-fs-read-stream.js b/test/parallel/test-fs-read-stream.js index 98a316e60e4a02..d64f77fe0c20a8 100644 --- a/test/parallel/test-fs-read-stream.js +++ b/test/parallel/test-fs-read-stream.js @@ -105,9 +105,9 @@ file6.on('end', function() { assert.equal(file6.data, 'yz\n'); }); -assert.throws(function() { +common.throws(function() { fs.createReadStream(rangeFile, {start: 10, end: 2}); -}, /"start" option must be <= "end" option/); +}, {code: 'INVALIDOPT'}); var stream = fs.createReadStream(rangeFile, { start: 0, end: 0 }); stream.data = ''; diff --git a/test/parallel/test-fs-watchfile.js b/test/parallel/test-fs-watchfile.js index d30261859c39b7..06340032432828 100644 --- a/test/parallel/test-fs-watchfile.js +++ b/test/parallel/test-fs-watchfile.js @@ -14,9 +14,9 @@ assert.throws(function() { fs.watchFile('./another-file', {}, 'bad listener'); }, /"watchFile\(\)" requires a listener function/); -assert.throws(function() { +common.throws(function() { fs.watchFile(new Object(), function() {}); -}, /Path must be a string/); +}, {code: 'INVALIDARG'}); const enoentFile = path.join(common.tmpDir, 'non-existent-file'); const expectedStatObject = new fs.Stats( diff --git a/test/parallel/test-fs-write-stream-throw-type-error.js b/test/parallel/test-fs-write-stream-throw-type-error.js index 007ac03f1bb342..c877b63e4357fd 100644 --- a/test/parallel/test-fs-write-stream-throw-type-error.js +++ b/test/parallel/test-fs-write-stream-throw-type-error.js @@ -18,18 +18,18 @@ assert.doesNotThrow(function() { fs.createWriteStream(example, {encoding: 'utf8'}); }); -assert.throws(function() { +common.throws(function() { fs.createWriteStream(example, null); -}, /"options" argument must be a string or an object/); -assert.throws(function() { +}, {code: 'INVALIDARG'}); +common.throws(function() { fs.createWriteStream(example, 123); -}, /"options" argument must be a string or an object/); -assert.throws(function() { +}, {code: 'INVALIDARG'}); +common.throws(function() { fs.createWriteStream(example, 0); -}, /"options" argument must be a string or an object/); -assert.throws(function() { +}, {code: 'INVALIDARG'}); +common.throws(function() { fs.createWriteStream(example, true); -}, /"options" argument must be a string or an object/); -assert.throws(function() { +}, {code: 'INVALIDARG'}); +common.throws(function() { fs.createWriteStream(example, false); -}, /"options" argument must be a string or an object/); +}, {code: 'INVALIDARG'}); diff --git a/test/parallel/test-http-agent-keepalive.js b/test/parallel/test-http-agent-keepalive.js index 2b939db68d7cbe..c7aba73b15a439 100644 --- a/test/parallel/test-http-agent-keepalive.js +++ b/test/parallel/test-http-agent-keepalive.js @@ -89,7 +89,7 @@ function remoteError() { }); req.on('error', function(err) { assert.ok(err); - assert.equal(err.message, 'socket hang up'); + assert.strictEqual(err.code, 'ECONNRESET'); assert.equal(agent.sockets[name].length, 1); assert.equal(agent.freeSockets[name], undefined); // Wait socket 'close' event emit diff --git a/test/parallel/test-http-url.parse-only-support-http-https-protocol.js b/test/parallel/test-http-url.parse-only-support-http-https-protocol.js index e7ed82817146ea..ddd11d551c3245 100644 --- a/test/parallel/test-http-url.parse-only-support-http-https-protocol.js +++ b/test/parallel/test-http-url.parse-only-support-http-https-protocol.js @@ -4,63 +4,29 @@ var assert = require('assert'); var http = require('http'); var url = require('url'); +function errChecker(protocol, expected) { + return function(err) { + if (err instanceof Error) { + console.log(err); + assert.strictEqual(err.code, 'HTTPUNSUPPORTEDPROTOCOL'); + assert.strictEqual(err.name, 'Error[HTTPUNSUPPORTEDPROTOCOL]'); + assert.strictEqual( + err.message, + `Protocol "${protocol}" is not supported. Expected "${expected}"`); + return true; + } + }; +} -assert.throws(function() { - http.request(url.parse('file:///whatever')); -}, function(err) { - if (err instanceof Error) { - assert.strictEqual(err.message, 'Protocol "file:" not supported.' + - ' Expected "http:"'); - return true; - } -}); - -assert.throws(function() { - http.request(url.parse('mailto:asdf@asdf.com')); -}, function(err) { - if (err instanceof Error) { - assert.strictEqual(err.message, 'Protocol "mailto:" not supported.' + - ' Expected "http:"'); - return true; - } -}); - -assert.throws(function() { - http.request(url.parse('ftp://www.example.com')); -}, function(err) { - if (err instanceof Error) { - assert.strictEqual(err.message, 'Protocol "ftp:" not supported.' + - ' Expected "http:"'); - return true; - } -}); - -assert.throws(function() { - http.request(url.parse('javascript:alert(\'hello\');')); -}, function(err) { - if (err instanceof Error) { - assert.strictEqual(err.message, 'Protocol "javascript:" not supported.' + - ' Expected "http:"'); - return true; - } -}); - -assert.throws(function() { - http.request(url.parse('xmpp:isaacschlueter@jabber.org')); -}, function(err) { - if (err instanceof Error) { - assert.strictEqual(err.message, 'Protocol "xmpp:" not supported.' + - ' Expected "http:"'); - return true; - } -}); - -assert.throws(function() { - http.request(url.parse('f://some.host/path')); -}, function(err) { - if (err instanceof Error) { - assert.strictEqual(err.message, 'Protocol "f:" not supported.' + - ' Expected "http:"'); - return true; - } +[ + ['file:', 'file:///whatever'], + ['mailto:', 'mailto:asdf@asdf.com'], + ['ftp:', 'ftp://www.example.org'], + ['javascript:', 'javascript:alert(\'hello\');'], + ['xmpp:', 'xmpp:example@jabber.org'], + ['f:', 'f://some.host/path'] +].forEach((item) => { + assert.throws(function() { + http.request(url.parse(item[1])); + }, errChecker(item[0], 'http:')); }); diff --git a/test/parallel/test-http-write-head.js b/test/parallel/test-http-write-head.js index a56d6f18d0e377..c3a316d232e858 100644 --- a/test/parallel/test-http-write-head.js +++ b/test/parallel/test-http-write-head.js @@ -25,7 +25,9 @@ var s = http.createServer(function(req, res) { res.setHeader('foo', undefined); } catch (e) { assert.ok(e instanceof Error); - assert.equal(e.message, '"value" required in setHeader("foo", value)'); + assert.strictEqual(e.code, 'REQUIREDARG'); + assert.strictEqual(e.message, + '"value" argument is required and cannot be undefined'); threw = true; } assert.ok(threw, 'Undefined value should throw'); diff --git a/test/parallel/test-internal-errors.js b/test/parallel/test-internal-errors.js new file mode 100644 index 00000000000000..cec874ad816c8f --- /dev/null +++ b/test/parallel/test-internal-errors.js @@ -0,0 +1,106 @@ +// Flags: --expose-internals +'use strict'; + +require('../common'); +const assert = require('assert'); + +const errors = require('internal/errors'); + +assert.ok(errors.Error); +assert.ok(errors.TypeError); +assert.ok(errors.RangeError); + +assert.throws(() => { + new errors.Error('SOME KEY THAT DOES NOT EXIST'); +}, /An invalid error message key was used/); + +// Test Unknown Signal error +{ + // Unknown signal: ${sig} + const err = new errors.Error('UNKNOWNSIGNAL', 'FOO'); + assert.strictEqual(err.name, 'Error[UNKNOWNSIGNAL]'); + assert.strictEqual(err.code, 'UNKNOWNSIGNAL'); + assert.strictEqual(err.message, 'Unknown signal: FOO'); +} + +// Test Invalid Argument errors +{ + // '${arg}' argument must be ${prefix} ${expected} + const err = new errors.TypeError('INVALIDARG', 'Foo', 'Bar'); + assert.strictEqual(err.name, 'TypeError[INVALIDARG]'); + assert.strictEqual(err.code, 'INVALIDARG'); + assert.strictEqual(err.message, '"Foo" argument must be a Bar'); +} + +{ + // '${arg}' argument must be ${prefix} ${expected} + const err = new errors.TypeError('INVALIDARG', 'Foo', 'Object'); + assert.strictEqual(err.name, 'TypeError[INVALIDARG]'); + assert.strictEqual(err.code, 'INVALIDARG'); + assert.strictEqual(err.message, '"Foo" argument must be an Object'); +} + +{ + // '${arg}' argument must be ${prefix} ${expected} + const err = new errors.TypeError('INVALIDARG', 'Foo', ['Object']); + assert.strictEqual(err.name, 'TypeError[INVALIDARG]'); + assert.strictEqual(err.code, 'INVALIDARG'); + assert.strictEqual(err.message, '"Foo" argument must be an Object'); +} + +{ + // '${arg}' argument must be one of: ${expected} + const err = new errors.TypeError('INVALIDARG', 'Foo', ['Object', 'Bar']); + assert.strictEqual(err.name, 'TypeError[INVALIDARG]'); + assert.strictEqual(err.code, 'INVALIDARG'); + assert.strictEqual(err.message, + '"Foo" argument must be one of: Object, Bar'); +} + +// Test Required Argument Error +{ + // `'${arg}' argument is required and cannot be undefined.` + const err = new errors.TypeError('REQUIREDARG', 'Foo'); + assert.strictEqual(err.name, 'TypeError[REQUIREDARG]'); + assert.strictEqual(err.code, 'REQUIREDARG'); + assert.strictEqual(err.message, + '"Foo" argument is required and cannot be undefined'); +} + +// Test Invalid Option Value Error +{ + // Invalid value for "${opt}" option. + const err = new errors.TypeError('INVALIDOPTVALUE', 'Foo'); + assert.strictEqual(err.name, 'TypeError[INVALIDOPTVALUE]'); + assert.strictEqual(err.code, 'INVALIDOPTVALUE'); + assert.strictEqual(err.message, + 'Invalid value for "Foo" option'); +} + +{ + // Invalid value for "${opt}" option: ${val}. + const err = new errors.TypeError('INVALIDOPTVALUE', 'Foo', 'Bar'); + assert.strictEqual(err.name, 'TypeError[INVALIDOPTVALUE]'); + assert.strictEqual(err.code, 'INVALIDOPTVALUE'); + assert.strictEqual(err.message, + 'Invalid value for "Foo" option: Bar'); +} + +// Test Invalid Argument Value Error +{ + // Invalid value for "${arg}" argument. + const err = new errors.TypeError('INVALIDARGVALUE', 'Foo'); + assert.strictEqual(err.name, 'TypeError[INVALIDARGVALUE]'); + assert.strictEqual(err.code, 'INVALIDARGVALUE'); + assert.strictEqual(err.message, + 'Invalid value for "Foo" argument'); +} + +{ + // Invalid value for "${arg}" argument: ${val}. + const err = new errors.TypeError('INVALIDARGVALUE', 'Foo', 'Bar'); + assert.strictEqual(err.name, 'TypeError[INVALIDARGVALUE]'); + assert.strictEqual(err.code, 'INVALIDARGVALUE'); + assert.strictEqual(err.message, + 'Invalid value for "Foo" argument: Bar'); +} diff --git a/test/parallel/test-net-create-connection.js b/test/parallel/test-net-create-connection.js index 6cc71c7afa8b73..0f84f68846d939 100644 --- a/test/parallel/test-net-create-connection.js +++ b/test/parallel/test-net-create-connection.js @@ -25,7 +25,7 @@ server.listen(tcpPort, 'localhost', function() { assert.throws(function() { net.createConnection(opts, cb); }, function(err) { - return err instanceof errtype && msg === err.message; + return err instanceof errtype && msg === err.code; }); } @@ -39,59 +39,59 @@ server.listen(tcpPort, 'localhost', function() { fail({ port: true - }, TypeError, '"port" option should be a number or string: true'); + }, TypeError, 'INVALIDOPT'); fail({ port: false - }, TypeError, '"port" option should be a number or string: false'); + }, TypeError, 'INVALIDOPT'); fail({ port: [] - }, TypeError, '"port" option should be a number or string: '); + }, TypeError, 'INVALIDOPT'); fail({ port: {} - }, TypeError, '"port" option should be a number or string: [object Object]'); + }, TypeError, 'INVALIDOPT'); fail({ port: null - }, TypeError, '"port" option should be a number or string: null'); + }, TypeError, 'INVALIDOPT'); fail({ port: '' - }, RangeError, '"port" option should be >= 0 and < 65536: '); + }, RangeError, 'PORTRANGE'); fail({ port: ' ' - }, RangeError, '"port" option should be >= 0 and < 65536: '); + }, RangeError, 'PORTRANGE'); fail({ port: '0x' - }, RangeError, '"port" option should be >= 0 and < 65536: 0x'); + }, RangeError, 'PORTRANGE'); fail({ port: '-0x1' - }, RangeError, '"port" option should be >= 0 and < 65536: -0x1'); + }, RangeError, 'PORTRANGE'); fail({ port: NaN - }, RangeError, '"port" option should be >= 0 and < 65536: NaN'); + }, RangeError, 'PORTRANGE'); fail({ port: Infinity - }, RangeError, '"port" option should be >= 0 and < 65536: Infinity'); + }, RangeError, 'PORTRANGE'); fail({ port: -1 - }, RangeError, '"port" option should be >= 0 and < 65536: -1'); + }, RangeError, 'PORTRANGE'); fail({ port: 65536 - }, RangeError, '"port" option should be >= 0 and < 65536: 65536'); + }, RangeError, 'PORTRANGE'); fail({ hints: (dns.ADDRCONFIG | dns.V4MAPPED) + 42, - }, TypeError, 'Invalid argument: hints must use valid flags'); + }, TypeError, 'INVALIDOPTVALUE'); }); // Try connecting to random ports, but do so once the server is closed diff --git a/test/parallel/test-net-listen-port-option.js b/test/parallel/test-net-listen-port-option.js index 18b256c973eaec..841d9664a540ca 100644 --- a/test/parallel/test-net-listen-port-option.js +++ b/test/parallel/test-net-listen-port-option.js @@ -16,9 +16,9 @@ net.Server().listen({ port: '' + common.PORT }, close); '+Infinity', '-Infinity' ].forEach(function(port) { - assert.throws(function() { + common.throws(function() { net.Server().listen({ port: port }, assert.fail); - }, /"port" argument must be >= 0 and < 65536/i); + }, {code: 'PORTRANGE'}); }); [null, true, false].forEach(function(port) { diff --git a/test/parallel/test-net-socket-write-error.js b/test/parallel/test-net-socket-write-error.js index db236be1a5e8f3..bdd542b59fb905 100644 --- a/test/parallel/test-net-socket-write-error.js +++ b/test/parallel/test-net-socket-write-error.js @@ -1,16 +1,15 @@ 'use strict'; const common = require('../common'); -const assert = require('assert'); const net = require('net'); const server = net.createServer().listen(common.PORT, connectToServer); function connectToServer() { const client = net.createConnection(common.PORT, () => { - assert.throws(() => { + common.throws(() => { client.write(1337); - }, /Invalid data, chunk must be a string or buffer, not number/); + }, {code: 'INVALIDARG'}); client.end(); }) diff --git a/test/parallel/test-path-parse-format.js b/test/parallel/test-path-parse-format.js index a42794790d1459..a86a31774b3ae5 100644 --- a/test/parallel/test-path-parse-format.js +++ b/test/parallel/test-path-parse-format.js @@ -162,10 +162,7 @@ function checkErrors(path) { path[errorCase.method].apply(path, errorCase.input); } catch (err) { assert.ok(err instanceof TypeError); - assert.ok( - errorCase.message.test(err.message), - 'expected ' + errorCase.message + ' to match ' + err.message - ); + assert.strictEqual(err.code, 'INVALIDARG'); return; } diff --git a/test/parallel/test-process-emitwarning.js b/test/parallel/test-process-emitwarning.js index 33547377bb901b..925253f5b5d1de 100644 --- a/test/parallel/test-process-emitwarning.js +++ b/test/parallel/test-process-emitwarning.js @@ -25,5 +25,5 @@ util.inherits(CustomWarning, Error); process.emitWarning(new CustomWarning()); // TypeError is thrown on invalid output -assert.throws(() => process.emitWarning(1), TypeError); -assert.throws(() => process.emitWarning({}), TypeError); +common.throws(() => process.emitWarning(1), {code: 'INVALIDARG'}); +common.throws(() => process.emitWarning({}), {code: 'INVALIDARG'}); diff --git a/test/parallel/test-readline-interface.js b/test/parallel/test-readline-interface.js index 9a3b7f5ef3ec2f..567994be5ad62a 100644 --- a/test/parallel/test-readline-interface.js +++ b/test/parallel/test-readline-interface.js @@ -1,6 +1,6 @@ // Flags: --expose_internals 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const readline = require('readline'); const internalReadline = require('internal/readline'); @@ -236,19 +236,12 @@ function isWarned(emitter) { // constructor throws if completer is not a function or undefined fi = new FakeInput(); - assert.throws(function() { + common.throws(function() { readline.createInterface({ input: fi, completer: 'string is not valid' }); - }, function(err) { - if (err instanceof TypeError) { - if (/Argument "completer" must be a function/.test(err)) { - return true; - } - } - return false; - }); + }, {type: TypeError, code: 'INVALIDARG'}); // sending a multi-byte utf8 char over multiple writes var buf = Buffer.from('☮', 'utf8'); diff --git a/test/parallel/test-regress-GH-5727.js b/test/parallel/test-regress-GH-5727.js index ae8cca9cbf3338..a6288b004e9875 100644 --- a/test/parallel/test-regress-GH-5727.js +++ b/test/parallel/test-regress-GH-5727.js @@ -4,7 +4,7 @@ const assert = require('assert'); const net = require('net'); const invalidPort = -1 >>> 0; -const errorMessage = /"port" argument must be \>= 0 and \< 65536/; +const errorMessage = {code: 'PORTRANGE'}; net.Server().listen(common.PORT, function() { const address = this.address(); @@ -15,16 +15,16 @@ net.Server().listen(common.PORT, function() { }); // The first argument is a configuration object -assert.throws(() => { +common.throws(() => { net.Server().listen({ port: invalidPort }, common.fail); }, errorMessage); // The first argument is the port, no IP given. -assert.throws(() => { +common.throws(() => { net.Server().listen(invalidPort, common.fail); }, errorMessage); // The first argument is the port, the second an IP. -assert.throws(() => { +common.throws(() => { net.Server().listen(invalidPort, '0.0.0.0', common.fail); }, errorMessage); diff --git a/test/parallel/test-timers-throw-when-cb-not-function.js b/test/parallel/test-timers-throw-when-cb-not-function.js index 13533107934bd6..c6dfb29be943a2 100644 --- a/test/parallel/test-timers-throw-when-cb-not-function.js +++ b/test/parallel/test-timers-throw-when-cb-not-function.js @@ -1,6 +1,5 @@ 'use strict'; -require('../common'); -const assert = require('assert'); +const common = require('../common'); function doSetTimeout(callback, after) { return function() { @@ -8,18 +7,18 @@ function doSetTimeout(callback, after) { }; } -assert.throws(doSetTimeout('foo'), - /"callback" argument must be a function/); -assert.throws(doSetTimeout({foo: 'bar'}), - /"callback" argument must be a function/); -assert.throws(doSetTimeout(), - /"callback" argument must be a function/); -assert.throws(doSetTimeout(undefined, 0), - /"callback" argument must be a function/); -assert.throws(doSetTimeout(null, 0), - /"callback" argument must be a function/); -assert.throws(doSetTimeout(false, 0), - /"callback" argument must be a function/); +common.throws(doSetTimeout('foo'), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetTimeout({foo: 'bar'}), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetTimeout(), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetTimeout(undefined, 0), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetTimeout(null, 0), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetTimeout(false, 0), + {code: 'CALLBACKREQUIRED'}); function doSetInterval(callback, after) { @@ -28,18 +27,18 @@ function doSetInterval(callback, after) { }; } -assert.throws(doSetInterval('foo'), - /"callback" argument must be a function/); -assert.throws(doSetInterval({foo: 'bar'}), - /"callback" argument must be a function/); -assert.throws(doSetInterval(), - /"callback" argument must be a function/); -assert.throws(doSetInterval(undefined, 0), - /"callback" argument must be a function/); -assert.throws(doSetInterval(null, 0), - /"callback" argument must be a function/); -assert.throws(doSetInterval(false, 0), - /"callback" argument must be a function/); +common.throws(doSetInterval('foo'), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetInterval({foo: 'bar'}), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetInterval(), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetInterval(undefined, 0), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetInterval(null, 0), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetInterval(false, 0), + {code: 'CALLBACKREQUIRED'}); function doSetImmediate(callback, after) { @@ -48,15 +47,15 @@ function doSetImmediate(callback, after) { }; } -assert.throws(doSetImmediate('foo'), - /"callback" argument must be a function/); -assert.throws(doSetImmediate({foo: 'bar'}), - /"callback" argument must be a function/); -assert.throws(doSetImmediate(), - /"callback" argument must be a function/); -assert.throws(doSetImmediate(undefined, 0), - /"callback" argument must be a function/); -assert.throws(doSetImmediate(null, 0), - /"callback" argument must be a function/); -assert.throws(doSetImmediate(false, 0), - /"callback" argument must be a function/); +common.throws(doSetImmediate('foo'), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetImmediate({foo: 'bar'}), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetImmediate(), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetImmediate(undefined, 0), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetImmediate(null, 0), + {code: 'CALLBACKREQUIRED'}); +common.throws(doSetImmediate(false, 0), + {code: 'CALLBACKREQUIRED'}); diff --git a/test/parallel/test-tls-basic-validations.js b/test/parallel/test-tls-basic-validations.js index a741f3b49c47ac..2c76d353189fc6 100644 --- a/test/parallel/test-tls-basic-validations.js +++ b/test/parallel/test-tls-basic-validations.js @@ -1,6 +1,6 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const tls = require('tls'); @@ -19,8 +19,8 @@ assert.throws(() => tls.createServer({key: 'dummykey', passphrase: 1}), assert.throws(() => tls.createServer({ecdhCurve: 1}), /TypeError: ECDH curve name must be a string/); -assert.throws(() => tls.createServer({handshakeTimeout: 'abcd'}), - /TypeError: handshakeTimeout must be a number/); +common.throws(() => tls.createServer({handshakeTimeout: 'abcd'}), + {code: 'INVALIDOPT'}); assert.throws(() => tls.createServer({sessionTimeout: 'abcd'}), /TypeError: Session timeout must be a 32-bit integer/); diff --git a/test/parallel/test-tty-stdout-end.js b/test/parallel/test-tty-stdout-end.js index a33a2e5ed27209..7084e53b7ac3ba 100644 --- a/test/parallel/test-tty-stdout-end.js +++ b/test/parallel/test-tty-stdout-end.js @@ -1,15 +1,5 @@ 'use strict'; // Can't test this when 'make test' doesn't assign a tty to the stdout. -require('../common'); -const assert = require('assert'); +const common = require('../common'); -const shouldThrow = function() { - process.stdout.end(); -}; - -const validateError = function(e) { - return e instanceof Error && - e.message === 'process.stdout cannot be closed.'; -}; - -assert.throws(shouldThrow, validateError); +common.throws(() => process.stdout.end(), {code: 'STDOUTCLOSE', type: Error});