Skip to content

Commit d3dc012

Browse files
committed
feat(models): ERR_INVALID_ARG_TYPE
Signed-off-by: Lexus Drumgold <[email protected]>
1 parent e162298 commit d3dc012

File tree

3 files changed

+265
-0
lines changed

3 files changed

+265
-0
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* @file Unit Tests - ERR_INVALID_ARG_TYPE
3+
* @module errnode/models/tests/unit/ERR_INVALID_ARG_TYPE
4+
*/
5+
6+
import { ErrorCode } from '#src/enums'
7+
import { determineSpecificType } from '#src/utils'
8+
import type { OneOrMany } from '@flex-development/tutils'
9+
import TestSubject from '../err-invalid-arg-type'
10+
11+
describe('unit:models/ERR_INVALID_ARG_TYPE', () => {
12+
let actual: unknown
13+
let name: string
14+
let type: string
15+
16+
beforeEach(() => {
17+
actual = null
18+
name = 'ip_addr'
19+
type = 'string'
20+
})
21+
22+
it('should return TypeError instance', () => {
23+
// Act
24+
const result = new TestSubject(name, type, actual)
25+
26+
// Expect
27+
expect(result).to.be.instanceof(TypeError)
28+
expect(result).to.have.property('name').equal('TypeError')
29+
})
30+
31+
it('should set error code', () => {
32+
expect(new TestSubject(name, type, actual))
33+
.to.have.property('code')
34+
.equal(ErrorCode.ERR_INVALID_ARG_TYPE)
35+
})
36+
37+
it('should set error message', () => {
38+
// Arrange
39+
const props = (name: string): string => `props.${name}`
40+
const received: string = `Received ${determineSpecificType(actual)}`
41+
const cases: [...ConstructorParameters<typeof TestSubject>, string][] = [
42+
[
43+
name,
44+
type,
45+
actual,
46+
`The '${name}' argument must be of type string. ${received}`
47+
],
48+
[
49+
props(name),
50+
type,
51+
actual,
52+
`The '${props(name)}' property must be of type string. ${received}`
53+
],
54+
[
55+
name,
56+
['RegExp', 'bigint', 'boolean', 'errno', 'number', 'string', 'symbol'],
57+
actual,
58+
`The '${name}' argument must be one of type bigint, boolean, number, string, or symbol or an instance of RegExp or errno. ${received}`
59+
],
60+
[
61+
name + ' argument',
62+
[
63+
'13',
64+
'26',
65+
'RegExp',
66+
'bigint',
67+
'boolean',
68+
'number',
69+
'object',
70+
'string',
71+
'symbol'
72+
],
73+
actual,
74+
`The ${name} argument must be one of type bigint, boolean, number, string, or symbol or an instance of RegExp or Object or one of 13 or 26. ${received}`
75+
]
76+
]
77+
78+
// Act + Expect
79+
cases.forEach(([name, types, actual, expected]) => {
80+
expect(new TestSubject(name, types, actual))
81+
.to.have.property('message')
82+
.equal(expected)
83+
})
84+
})
85+
86+
it('should throw if expected is not a string or string[]', () => {
87+
// Arrange
88+
const fn = TestSubject.bind({}, name, actual as OneOrMany<string>, actual)
89+
90+
// Expect
91+
expect(fn).to.throw('`expected` must be a string or string[]')
92+
})
93+
94+
it('should throw if name is not a string', () => {
95+
// Arrange
96+
const fn = TestSubject.bind({}, actual as string, [], actual)
97+
98+
// Expect
99+
expect(fn).to.throw("'name' must be a string")
100+
})
101+
})

src/models/err-invalid-arg-type.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/**
2+
* @file Error Models - ERR_INVALID_ARG_TYPE
3+
* @module errnode/models/ERR_INVALID_ARG_TYPE
4+
* @see https://github.com/nodejs/node/blob/v19.3.0/lib/internal/errors.js#L1197-L1286
5+
*/
6+
7+
import { ErrorCode } from '#src/enums'
8+
import formatList from '#src/internal/format-list'
9+
import type { MessageFn, NodeError, NodeErrorConstructor } from '#src/types'
10+
import { createNodeError, determineSpecificType } from '#src/utils'
11+
import type { OneOrMany } from '@flex-development/tutils'
12+
13+
/**
14+
* `ERR_INVALID_ARG_TYPE` model.
15+
*
16+
* Thrown when an argument of the wrong type is passed to a Node.js API.
17+
*
18+
* @see https://nodejs.org/api/errors.html#err_invalid_arg_type
19+
*
20+
* @class
21+
*
22+
* @param {string} name - Name of invalid argument or property
23+
* @param {OneOrMany<string>} expected - Expected type(s)
24+
* @param {unknown} actual - Value supplied by user
25+
* @return {NodeError<TypeError>} `TypeError` instance
26+
*/
27+
const ERR_INVALID_ARG_TYPE: NodeErrorConstructor<
28+
TypeErrorConstructor,
29+
MessageFn<[string, OneOrMany<string>, unknown]>
30+
> = createNodeError(
31+
ErrorCode.ERR_INVALID_ARG_TYPE,
32+
TypeError,
33+
/**
34+
* Creates an [`ERR_INVALID_ARG_TYPE`][1] message.
35+
*
36+
* [1]: https://nodejs.org/api/errors.html#err_invalid_arg_type
37+
*
38+
* @param {string} name - Name of invalid argument or property
39+
* @param {OneOrMany<string>} expected - Expected type(s)
40+
* @param {unknown} actual - Value supplied by user
41+
* @return {string} Error message
42+
* @throws {TypeError} If `name` is not a string, `expected` is not a string,
43+
* or `expected` is not an array containing only string values
44+
*/
45+
(name: string, expected: OneOrMany<string>, actual: unknown): string => {
46+
// ensure name is a string
47+
if (typeof name !== 'string') throw new TypeError("'name' must be a string")
48+
49+
// ensure expected is an array
50+
if (!Array.isArray(expected)) expected = [expected]
51+
52+
/**
53+
* Primitive value names.
54+
*
55+
* Sorted by a rough estimate on most frequently used entries.
56+
*
57+
* @const {Set<string>} k_types
58+
*/
59+
const k_types: Set<string> = new Set([
60+
'string',
61+
'function',
62+
'number',
63+
'object',
64+
'Function',
65+
'Object',
66+
'boolean',
67+
'bigint',
68+
'symbol'
69+
])
70+
71+
/**
72+
* Error message.
73+
*
74+
* @var {string} msg
75+
*/
76+
let msg: string = 'The '
77+
78+
// stylize invalid argument name
79+
msg += name.endsWith(' argument')
80+
? name
81+
: `'${name}' ${name.includes('.') ? 'property' : 'argument'}`
82+
83+
// continue building error message
84+
msg += ' must be '
85+
86+
/**
87+
* Names of expected class instances.
88+
*
89+
* @const {string[]} instances
90+
*/
91+
const instances: string[] = []
92+
93+
/**
94+
* Names of other expected types.
95+
*
96+
* @const {string[]} other
97+
*/
98+
const other: string[] = []
99+
100+
/**
101+
* Names of expected primitive types.
102+
*
103+
* @const {string[]} types
104+
*/
105+
const types: string[] = []
106+
107+
// get expected types
108+
for (const value of expected) {
109+
if (typeof value !== 'string') {
110+
throw new TypeError('`expected` must be a string or string[]')
111+
}
112+
113+
if (k_types.has(value)) types.push(value.toLowerCase())
114+
else if (/^([A-Z][\da-z]*)+$/.exec(value)) instances.push(value)
115+
else other.push(value)
116+
}
117+
118+
// special case: handle `object` in case other instances are allowed to
119+
// outline the differences between each other
120+
if (instances.length > 0) {
121+
/**
122+
* Position of `'object'` in {@linkcode types}.
123+
*
124+
* @const {number} pos
125+
*/
126+
const pos: number = types.indexOf('object')
127+
128+
// replace 'object' in types with 'Object' in instances
129+
if (pos !== -1) {
130+
types.splice(pos, 1)
131+
instances.push('Object')
132+
}
133+
}
134+
135+
// add expected primitive types to error message
136+
if (types.length > 0) {
137+
msg += `${types.length > 1 ? 'one ' : ''}of type`
138+
msg += ` ${formatList(types, 'or')}`
139+
if (instances.length > 0 || other.length > 0) msg += ' or '
140+
}
141+
142+
// add expected class instances to error message
143+
if (instances.length > 0) {
144+
msg += `an instance of ${formatList(instances, 'or')}`
145+
if (other.length > 0) msg += ' or '
146+
}
147+
148+
// add other expected types to error message
149+
if (other.length > 0) {
150+
if (other.length > 1) {
151+
msg += `one of ${formatList(other, 'or')}`
152+
} else {
153+
/* c8 ignore next */
154+
if (other[0]!.toLowerCase() !== other[0]) msg += 'an '
155+
msg += `${other[0]}`
156+
}
157+
}
158+
159+
return `${msg}. Received ${determineSpecificType(actual)}`
160+
}
161+
)
162+
163+
export default ERR_INVALID_ARG_TYPE

src/models/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export { default as ERR_IMPORT_ASSERTION_TYPE_FAILED } from './err-import-assert
1212
export { default as ERR_IMPORT_ASSERTION_TYPE_MISSING } from './err-import-assertion-type-missing'
1313
export { default as ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED } from './err-import-assertion-type-unsupported'
1414
export { default as ERR_INCOMPATIBLE_OPTION_PAIR } from './err-incompatible-option-pair'
15+
export { default as ERR_INVALID_ARG_TYPE } from './err-invalid-arg-type'
1516
export { default as ERR_INVALID_ARG_VALUE } from './err-invalid-arg-value'
1617
export { default as ERR_INVALID_MODULE_SPECIFIER } from './err-invalid-module-specifier'
1718
export { default as ERR_INVALID_PACKAGE_CONFIG } from './err-invalid-package-config'

0 commit comments

Comments
 (0)