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