-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
110 lines (91 loc) · 3.36 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
'use strict';
module.exports = function argsert (typeConfig, ...args) {
if (typeof typeConfig !== 'string' || (!isRequired(typeConfig) && !isOptional(typeConfig))) {
args = [typeConfig];
typeConfig = '';
}
const types = getTypes(typeConfig);
const configuredKeys = Object.keys(types);
const numRequired = configuredKeys.filter(k => 'required' in types[k]).length;
args = compactArgs(args);
if (args.length < numRequired) {
throw new Error(`Not enough arguments provided. Expected ${numRequired} but received ${args.length}.`);
}
if (args.length > configuredKeys.length) {
throw new Error(`Too many arguments provided. Expected max ${configuredKeys.length} but received ${args.length}.`);
}
args.forEach((arg, index) => {
const observedType =
Array.isArray(arg) ? 'array'
: arg === null ? 'null'
: arg instanceof Error ? 'error'
: isPromise(arg) ? 'promise'
: Buffer.isBuffer(arg) ? 'buffer'
: typeof arg;
const typesAtIndex = types[index];
const errorMessage = invalidArgMessage.bind(this, positionName(index), typesAtIndex, observedType);
if ('required' in typesAtIndex) {
const required = typesAtIndex.required;
if (required.indexOf(observedType) < 0 && required.indexOf('*') < 0) {
throw new TypeError(errorMessage('required'));
}
}
if ('optional' in typesAtIndex) {
const optional = typesAtIndex.optional;
if (arg !== undefined && optional.indexOf('*') < 0 && optional.indexOf(observedType) < 0) {
throw new TypeError(errorMessage('optional'));
}
}
});
return true;
};
function getTypes (typeConfig) {
return typeConfig.split(' ').reduce((result, str, index) => {
if (isOptional(str)) {
return Object.assign({}, result, {
[index]: {
optional: str.split('|').map(s => s.replace('[', '').replace(']', ''))
}
});
} else if (isRequired(str)) {
return Object.assign({}, result, {
[index]: {
required: str.split('|').map(s => s.replace('<', '').replace('>', ''))
}
});
} else if (str === '') {
return result;
} else {
throw new Error(`Invalid type config in the ${positionName(index)} position.`);
}
}, {});
}
function compactArgs (args) {
const lastArg = args[args.length - 1];
if (lastArg === undefined || lastArg === '') {
return args.filter(arg => arg !== undefined && arg !== '');
}
return args;
}
function invalidArgMessage (position, types, observed, kind) {
return `Invalid ${position} argument. Expected ${expectedTypes(types, kind)} but received ${observed}.`;
}
function positionName (index) {
const positionNames = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth'];
return positionNames[index] || 'manyith';
}
function expectedTypes (types, kind) {
return types[kind]
.concat(kind === 'optional' && types[kind].indexOf('undefined') < 0 ? 'undefined' : [])
.join(' or ');
}
function isOptional (arg) {
return arg.match(/^\[(\w+|\*)((?:\|(\w+))*?)]/);
}
function isRequired (arg) {
return arg.match(/^<(\w+|\*)((?:\|(\w+))*?)>/);
}
// 'borrowed' from: https://github.com/then/is-promise/commit/ed0eaa4dec17597f0dae892a0472a9b7f459320d
function isPromise (obj) {
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
}