/
Interface.js
112 lines (96 loc) · 3.68 KB
/
Interface.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
111
112
/**
* (note : Interface.js was extracted from one of my upcoming project: Spid.
* note to myself : I should really put it in its own package... #lazy)
*
* Usage:
*
* var MyInterface = Interface('MyInterface', {
* 'key': function(a, b){}
* });
* @param {String} name
* @param {Object} props
* attributes or methods
*/
var Interface = function Interface(name, props) {
if(!(this instanceof Interface)){
return new Interface(name, props);
}
if (arguments.length !== 2) {
throw new Error("Interface constructor called with " + arguments.length + "arguments, but expected exactly 2.");
}
this.name = name;
this.props = [];
for (var key in props) {
if (props.hasOwnProperty(key)) {
if (typeof key !== 'string' || (typeof props[key] !== 'string' && typeof props[key] !== 'object' && typeof props[key] !== 'function' && typeof props[key] !== 'number')) {
throw new Error("Interface constructor expects method to be " +
"passed in as a key value pairs. (insupported value type : " + typeof props[key] + ")");
}
this.props.push({key : key, value : props[key]});
}
}
};
/**
* Helpers for Interface.ensureImplements
* @param {Function} ctor [description]
* @throws {Error} see @Interface.ensureImplements
* @return {Function} ctor
*/
Interface.prototype.ensureImplements = function(ctor){
return Interface.ensureImplements(this, ctor);
};
var FUNCTION = 'function';
// var OBJECT = 'object';
// var STRING = 'string';
/**
* Ensure that `ctor` implements `ifaces` interfaces
* @param {Interface|Array[Interface]} ifaces interfaces name
* @param {Function|Object} ctor Constructor to check against or Object
* @throws {Error} If the object does not implement the interface
* @chainable
* @return {Function} ctor
*/
Interface.ensureImplements = function(ifaces, objOrCtor) {
// syntaxic sugar
if(!Array.isArray(ifaces)){
ifaces = [ifaces];
}
function getProp(objOrCtor, prop){
return objOrCtor.prototype ? objOrCtor.prototype[prop] : objOrCtor[prop];
}
function getName(objOrCtor){
return objOrCtor.name || objOrCtor.__name__;
}
function getHumanName(objOrCtor){
return typeof objOrCtor === 'function' ?
'for instance of "' + getName(objOrCtor) + '"'
: 'in object "' + getName(objOrCtor) + '"';
}
ifaces.forEach(function(iface){
iface.props.forEach(function(prop){
var name = prop.key; // attribute or method name
var value = prop.value; // attribute (string|object) or method value (function)
var valueType = typeof value; // string | object | function
var propExist = !!getProp(objOrCtor, name);
// check method
if(valueType === FUNCTION){
// present but not method
if (propExist && typeof getProp(objOrCtor, name) !== FUNCTION) {
throw new Error('Interface #<'+iface.name+'> requires a method "' + name + '", found "' + (typeof getProp(objOrCtor, name)) + '"');
}
// present as a method, but wrong argument number
if(propExist && value.length !== getProp(objOrCtor, name).length){
throw new Error('Interface #<'+iface.name+'> requires a method "' + name + '" with "'+ value.length + '" argument(s), found "' + getProp(objOrCtor, name).length + '"');
}
// present as a method with the right number of arguments
if(propExist){
return;
}
throw new Error('Interface #<'+iface.name+'> requires a method "' + name + '" ' + getHumanName(objOrCtor));
}
throw new Error('Interface #<'+iface.name+'> requires an attribute "' + name + '" ' + getHumanName(objOrCtor));
});
});
return objOrCtor;
};
module.exports = Interface;