dbc
Version:
Design by contract and type checking assertions
534 lines (415 loc) • 22.4 kB
HTML
<html>
<head>
<title>dbc</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">
<link rel="stylesheet" media="all" href="docco.css" />
</head>
<body>
<div id="container">
<div id="background"></div>
<ul class="sections">
<li id="section-1">
<div class="annotation">
<div class="pilwrap for-h1">
<a class="pilcrow" href="#section-1">¶</a>
</div>
<h1>dbc</h1>
<p><code>dbc</code> is a small library for design-by-contract style defensive coding in javascript. </p>
</div>
<div class="content"><div class='highlight'><pre>
(<span class="function"><span class="keyword">function</span> <span class="params">()</span> {</span>
<span class="keyword">var</span> mode
, messages = [];
<span class="keyword">var</span> _ = <span class="keyword">this</span>._ || require(<span class="string">'underscore'</span>);
dbc = {</pre></div></div>
</li>
<li id="section-2">
<div class="annotation">
<div class="pilwrap for-h1">
<a class="pilcrow" href="#section-2">¶</a>
</div>
<h1>Public API</h1>
</div>
</li>
<li id="section-3">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-3">¶</a>
</div>
<h2>check</h2>
<p>Check asserts that an object <code>o</code> meets a specification <code>spec</code>.
If <code>o</code> does not satisfy <code>spec</code> then an exception is thrown.
eg.</p>
<pre><code> dbc.check({
name: 'John',
age: 22
}, {
name: [{validator:'type',args:['string']}],
age: [{validator:'type',args:['number']}],
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> check: <span class="keyword">function</span>(o, spec, message) {
message = message || <span class="string">''</span>
mode = <span class="string">'check'</span>;
<span class="keyword">try</span> {
applyValidators.call(<span class="keyword">this</span>, o, spec);
} <span class="keyword">catch</span> (e) {
<span class="keyword">throw</span> <span class="keyword">new</span> Error(message ? message + <span class="string">': '</span> + e.message : e.message);
}
},</pre></div></div>
</li>
<li id="section-4">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-4">¶</a>
</div>
<h2>validate</h2>
<p>Validate is the same as check, except that errors are returned
as an array of messages instead of throwing an exception.</p>
</div>
<div class="content"><div class='highlight'><pre> validate: <span class="function"><span class="keyword">function</span> <span class="params">(o, spec)</span> {</span>
mode = <span class="string">'validate'</span>;
applyValidators.call(<span class="keyword">this</span>, o, spec);
<span class="keyword">return</span> messages;
},</pre></div></div>
</li>
<li id="section-5">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-5">¶</a>
</div>
<h2>wrap</h2>
<p>Wrap returns a wrapped function that applies 'specs' validators to the
functions arguments and 'returnSpec' validators to the return value.
eg</p>
<pre><code> var add = dbc.wrap(function (f,s) {
return f + s;
}, {
// validators for the first arg
0: [{validator:'type', args:['number']}],
// validators for the second arg
1:[{validator:'type', args:['number']}]
},
// validators for the return value
[{validator: 'type', args:['number']}]);</code></pre>
</div>
<div class="content"><div class='highlight'><pre> wrap: <span class="keyword">function</span>(original, specs, returnSpec) {
<span class="keyword">return</span> <span class="function"><span class="keyword">function</span> <span class="params">()</span> {</span>
<span class="keyword">var</span> r;
checkArgs(arguments);
r = original.apply(<span class="keyword">this</span>,arguments);
checkReturn();
<span class="keyword">return</span> r;
<span class="function"><span class="keyword">function</span> <span class="title">checkArgs</span><span class="params">(args)</span> {</span>
_.each(_.keys(specs || {}), <span class="function"><span class="keyword">function</span> <span class="params">(k,index)</span> {</span>
dbc.check(
{k: args[index] },
{ k: specs[k] });
});
}
<span class="function"><span class="keyword">function</span> <span class="title">checkReturn</span><span class="params">()</span> {</span>
<span class="keyword">if</span> (returnSpec) {
dbc.check({ret: r}, {ret: returnSpec});
}
}
};
},</pre></div></div>
</li>
<li id="section-6">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-6">¶</a>
</div>
<h2>makeConstructor</h2>
<p>MakeConstructor generates a constructor from a spec. The constructor
validates that the created objects meet the spec.
ie </p>
<pre><code> var Person = dbc.MakeConstructor({
name: [{validator:'type',args:'string'}],
age: [{validator:'type',args:'number'}]
});
Person.prototype.canDrive = dbc.wrap(function () {
return this.age > 16;
}, null, [{validator:'type',args:'number'}]);
var p = new Person('John', 22);
p.canDrive(); // true </code></pre>
</div>
<div class="content"><div class='highlight'><pre> makeConstructor: <span class="keyword">function</span>(spec) {
<span class="keyword">var</span> f = <span class="function"><span class="keyword">function</span> <span class="params">(prps)</span> {</span>
<span class="keyword">var</span> c = <span class="keyword">this</span>;
_.each(_.keys(spec), <span class="function"><span class="keyword">function</span> <span class="params">(key)</span> {</span>
c[key] = prps[key];
});
dbc.check(c, c.__spec);
};
f.prototype.__spec = spec;
<span class="keyword">return</span> f;
},</pre></div></div>
</li>
<li id="section-7">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-7">¶</a>
</div>
<h2>custom</h2>
<p>The <code>custom</code> validator applies a custom predicate.
eg</p>
<pre><code> dbc.check({
number: 7
},{
number: [{validator:'custom',args:[function isEven(n) {
return n % 2 === 0;
}]}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> custom: <span class="keyword">function</span>(v, test, message) {
<span class="keyword">if</span> (!isExisting(v)) <span class="keyword">return</span>;
<span class="keyword">if</span> (!test(v)) {
storeMessage(message || <span class="string">'failed custom function condition for value '</span> + v);
}
},</pre></div></div>
</li>
<li id="section-8">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-8">¶</a>
</div>
<h2>assert</h2>
<p>Assert is for simple boolean assertions. eg</p>
<pre><code> dbc.assert(6 === 9, 'Six should equal nine');</code></pre>
</div>
<div class="content"><div class='highlight'><pre> assert: <span class="keyword">function</span>(condition, message) {
<span class="keyword">if</span> (!condition) {
storeMessage(message);
}
},</pre></div></div>
</li>
<li id="section-9">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-9">¶</a>
</div>
<h2>type</h2>
<p>Type asserts the type of a value using JavaScript's <code>typeof</code> operator.
In addition to the JavaScript types (undefined, object, boolean, number, string, function) you can also use <code>array</code>. eg</p>
<pre><code> dbc.check({
name:'John'
}, {
name: [{validator:'type',args: ['string']}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> type: <span class="function"><span class="keyword">function</span> <span class="params">(v, type, message)</span> {</span>
<span class="keyword">if</span> (type.charAt(type.length-<span class="number">1</span>) == <span class="string">'?'</span>) {
<span class="keyword">if</span> (!isExisting(v)) <span class="keyword">return</span>;
type = type.substring(<span class="number">0</span>, type.length-<span class="number">1</span>);
}
message = message || <span class="string">'Expected type of '</span> + type + <span class="string">' but was '</span> + <span class="keyword">typeof</span> v;
<span class="keyword">if</span> (type == <span class="string">'array'</span>) {
<span class="keyword">if</span> (!_.isArray(v)) {
storeMessage(message)
}
<span class="keyword">return</span>;
}
<span class="keyword">if</span> (<span class="keyword">typeof</span> v == <span class="string">'undefined'</span> || v == <span class="literal">null</span>) {
storeMessage(<span class="string">'Expected type of '</span> + type + <span class="string">' but was null or undefined'</span>);
}
<span class="keyword">if</span> ((<span class="keyword">typeof</span> v) != type) {
storeMessage(message);
}
},</pre></div></div>
</li>
<li id="section-10">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-10">¶</a>
</div>
<h2>required</h2>
<p>Required asserts that a value is not null or undefined. This validator does
not require any args. eg</p>
<pre><code> dbc.check({
name:'John'
}, {
name: [{validator:'required'}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> required: <span class="keyword">function</span>(v, message) {
<span class="keyword">if</span> (!isExisting(v)) {
storeMessage(message || <span class="string">'expected a defined value'</span>);
}
},</pre></div></div>
</li>
<li id="section-11">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-11">¶</a>
</div>
<h2>isArray</h2>
<p>IsArray asserts that a value is an array. This validator does
not require any args. eg</p>
<pre><code> dbc.check({
colours: ['red','green','blue']
}, {
colours: [{validator:'isArray'}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> isArray: <span class="function"><span class="keyword">function</span> <span class="params">(v, message)</span> {</span>
<span class="keyword">if</span> (isExisting(v) && !_.isArray(v)) {
storeMessage(message || <span class="string">'expected an array'</span>)
}
},</pre></div></div>
</li>
<li id="section-12">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-12">¶</a>
</div>
<h2>isEnumerable</h2>
<p>IsEnumerable asserts that a value has a forEach function. This validator does
not require any args. eg</p>
<pre><code> dbc.check({
colours: ['red','green','blue']
}, {
colours: [{validator:'isEnumerable'}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> isEnumerable: <span class="function"><span class="keyword">function</span> <span class="params">(v, message)</span> {</span>
<span class="keyword">if</span> (isExisting(v) && <span class="keyword">typeof</span> v.forEach !== <span class="string">'function'</span>) {
storeMessage(message || <span class="string">'expected an object with a forEach function'</span>);
}
},</pre></div></div>
</li>
<li id="section-13">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-13">¶</a>
</div>
<h2>isNonEmptyCollection</h2>
<p>isNonEmptyCollection asserts that a value has a length property greater than zero. This validator does
not require any args. eg</p>
<pre><code> dbc.check({
divs: $('div') // assuming jQuery
}, {
divs: [{validator:'isNonEmptyCollection'}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> isNonEmptyCollection: <span class="function"><span class="keyword">function</span> <span class="params">(v, message)</span> {</span>
<span class="keyword">if</span> (!isExisting(v)) <span class="keyword">return</span>;
<span class="keyword">if</span> (!(<span class="keyword">typeof</span> v.length === <span class="string">'number'</span> && v.length > <span class="number">0</span>)) {
<span class="keyword">throw</span> <span class="keyword">new</span> Error(message || <span class="string">'expected collection with length > 0'</span>);
}
},</pre></div></div>
</li>
<li id="section-14">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-14">¶</a>
</div>
<h2>isFunction</h2>
<p>isFunction asserts that a value is a function. This validator does
not require any args. eg</p>
<pre><code> dbc.check({
square: function (n) { return n * n; }
}, {
square: [{validator:'isFunction'}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> isFunction: <span class="keyword">function</span>(f, message) {
<span class="keyword">this</span>.type(f, <span class="string">'function'</span>, message || <span class="string">'expected a function'</span>);
},</pre></div></div>
</li>
<li id="section-15">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-15">¶</a>
</div>
<h2>isObject</h2>
<p>isObject asserts that a value is an object. This validator does
not require any args. eg</p>
<pre><code> dbc.check({
thing: {}
}, {
thing: [{validator:'isObject'}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> isObject: <span class="keyword">function</span>(o, message) {
<span class="keyword">this</span>.type(o, <span class="string">'object'</span>, message || <span class="string">'expected an object'</span>);
},</pre></div></div>
</li>
<li id="section-16">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-16">¶</a>
</div>
<h2>isInstance</h2>
<p>isInstance asserts the constructor of an object. eg</p>
<pre><code> dbc.check({
john: new Person('John')
}, {
john: [{validator:'isInstance',args:[Person]}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> isInstance: <span class="keyword">function</span>(o, type, message) {
<span class="keyword">if</span> (isExisting(o) && !(o <span class="keyword">instanceof</span> type)) {
storeMessage(message || <span class="string">"expected "</span> + o + <span class="string">" to be an instance of "</span> + type.name);
}
},</pre></div></div>
</li>
<li id="section-17">
<div class="annotation">
<div class="pilwrap for-h2">
<a class="pilcrow" href="#section-17">¶</a>
</div>
<h2>functionArity</h2>
<p>functionArity asserts the arity of a function. eg</p>
<pre><code> dbc.check({
add: function (f,s) { return f + s; }
}, {
add: [{validator:'functionArity',args:[2]}]
});</code></pre>
</div>
<div class="content"><div class='highlight'><pre> functionArity: <span class="function"><span class="keyword">function</span> <span class="params">(f, arity, message)</span> {</span>
<span class="keyword">if</span> (!isExisting(f)) <span class="keyword">return</span>;
<span class="keyword">this</span>.isFunction(f, <span class="string">'cannot check arity of an object that is not a function'</span>);
<span class="keyword">if</span> (f.length !== arity) {
storeMessage(message || <span class="string">'Function arity is '</span> + f.length + <span class="string">'. Expected '</span> + arity);
}
}
};
<span class="function"><span class="keyword">function</span> <span class="title">isExisting</span><span class="params">(v)</span> {</span>
<span class="keyword">return</span> <span class="keyword">typeof</span> v !== <span class="string">"undefined"</span> && v !== <span class="literal">null</span>;
}
<span class="function"><span class="keyword">function</span> <span class="title">applyValidators</span><span class="params">(o, spec)</span> {</span>
<span class="keyword">var</span> specKeys = _.keys(spec),
dbc = <span class="keyword">this</span>;
specKeys.forEach(<span class="function"><span class="keyword">function</span> <span class="params">(key)</span> {</span>
<span class="keyword">var</span> validators = spec[key];
validators.forEach(<span class="keyword">function</span>(validator) {
dbc[validator.validator].apply(dbc, [o[key]].concat(validator.args || []))
});
});
}
<span class="function"><span class="keyword">function</span> <span class="title">storeMessage</span><span class="params">(message)</span> {</span>
<span class="keyword">if</span> (mode === <span class="string">'validate'</span>) {
messages.push(message);
<span class="keyword">return</span>;
}
<span class="keyword">throw</span> <span class="keyword">new</span> Error(message);
}
<span class="keyword">if</span> (<span class="keyword">typeof</span> define !== <span class="string">"undefined"</span> && define !== <span class="literal">null</span>) {
define(<span class="string">'dbc'</span>, [], <span class="function"><span class="keyword">function</span> <span class="params">()</span> {</span>
<span class="keyword">return</span> dbc;
});
} <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> window !== <span class="string">"undefined"</span> && window !== <span class="literal">null</span>) {
window.dbc = dbc;
}
<span class="keyword">if</span> (<span class="keyword">typeof</span> module !== <span class="string">"undefined"</span> && module !== <span class="literal">null</span>) {
module.exports = dbc;
}
})();</pre></div></div>
</li>
</ul>
</div>
</body>
</html>