1# Using the internal/errors.js module 2 3## What is internal/errors.js 4 5The `require('internal/errors')` module is an internal-only module that can be 6used to produce `Error`, `TypeError` and `RangeError` instances that use a 7static, permanent error code and an optionally parameterized message. 8 9The intent of the module is to allow errors provided by Node.js to be assigned a 10permanent identifier. Without a permanent identifier, userland code may need to 11inspect error messages to distinguish one error from another. An unfortunate 12result of that practice is that changes to error messages result in broken code 13in the ecosystem. For that reason, Node.js has considered error message changes 14to be breaking changes. By providing a permanent identifier for a specific 15error, we reduce the need for userland code to inspect error messages. 16 17Switching an existing error to use the `internal/errors` module must be 18considered a `semver-major` change. 19 20## Using internal/errors.js 21 22The `internal/errors` module exposes all custom errors as subclasses of the 23builtin errors. After being added, an error can be found in the `codes` object. 24 25For instance, an existing `Error` such as: 26 27```js 28const err = new TypeError(`Expected string received ${type}`); 29``` 30 31Can be replaced by first adding a new error key into the `internal/errors.js` 32file: 33 34```js 35E('FOO', 'Expected string received %s', TypeError); 36``` 37 38Then replacing the existing `new TypeError` in the code: 39 40```js 41const { FOO } = require('internal/errors').codes; 42// ... 43const err = new FOO(type); 44``` 45 46## Adding new errors 47 48New static error codes are added by modifying the `internal/errors.js` file 49and appending the new error codes to the end using the utility `E()` method. 50 51```js 52E('EXAMPLE_KEY1', 'This is the error value', TypeError); 53E('EXAMPLE_KEY2', (a, b) => `${a} ${b}`, RangeError); 54``` 55 56The first argument passed to `E()` is the static identifier. The second 57argument is either a String with optional `util.format()` style replacement 58tags (e.g. `%s`, `%d`), or a function returning a String. The optional 59additional arguments passed to the `errors.message()` function (which is 60used by the `errors.Error`, `errors.TypeError` and `errors.RangeError` classes), 61will be used to format the error message. The third argument is the base class 62that the new error will extend. 63 64It is possible to create multiple derived 65classes by providing additional arguments. The other ones will be exposed as 66properties of the main class: 67 68<!-- eslint-disable no-unreachable --> 69 70```js 71E('EXAMPLE_KEY', 'Error message', TypeError, RangeError); 72 73// In another module 74const { EXAMPLE_KEY } = require('internal/errors').codes; 75// TypeError 76throw new EXAMPLE_KEY(); 77// RangeError 78throw new EXAMPLE_KEY.RangeError(); 79``` 80 81## Documenting new errors 82 83Whenever a new static error code is added and used, corresponding documentation 84for the error code should be added to the `doc/api/errors.md` file. This will 85give users a place to go to easily look up the meaning of individual error 86codes. 87 88In case `make lint` fails to detect the new error codes added into `errors.md`, 89the markdown linting cache must be cleaned with `make lint-md-clean`. 90 91## Testing new errors 92 93When adding a new error, corresponding test(s) for the error message 94formatting may also be required. If the message for the error is a 95constant string then no test is required for the error message formatting 96as we can trust the error helper implementation. An example of this kind of 97error would be: 98 99```js 100E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound'); 101``` 102 103If the error message is not a constant string then tests to validate 104the formatting of the message based on the parameters used when 105creating the error should be added to 106`test/parallel/test-internal-errors.js`. These tests should validate 107all of the different ways parameters can be used to generate the final 108message string. A simple example is: 109 110```js 111// Test ERR_TLS_CERT_ALTNAME_INVALID 112assert.strictEqual( 113 errors.message('ERR_TLS_CERT_ALTNAME_INVALID', ['altname']), 114 'Hostname/IP does not match certificate\'s altnames: altname'); 115``` 116 117In addition, there should also be tests which validate the use of the 118error based on where it is used in the codebase. If the error message is 119static, these tests should only validate that the expected code is received 120and NOT validate the message. This will reduce the amount of test change 121required when the message for an error changes. 122 123```js 124assert.throws(() => { 125 socket.bind(); 126}, common.expectsError({ 127 code: 'ERR_SOCKET_ALREADY_BOUND', 128 type: Error, 129})); 130``` 131 132Avoid changing the format of the message after the error has been created. 133If it does make sense to do this for some reason, then additional tests 134validating the formatting of the error message for those cases will 135likely be required. 136 137## API 138 139### Object: errors.codes 140 141Exposes all internal error classes to be used by Node.js APIs. 142 143### Method: errors.message(key, args) 144 145* `key` {string} The static error identifier 146* `args` {Array} Zero or more optional arguments passed as an Array 147* Returns: {string} 148 149Returns the formatted error message string for the given `key`. 150