JSDoc-compliant run-time code contracts for JavaScript
JavaScript TypeScript HTML
Permalink
Failed to load latest commit information.
dist
tests
ts
typings
.gitignore
.travis.yml
Gruntfile.js
README.md
TYPESCRIPT.md
byContract.js
index.d.ts
package.json
tsconfig.json
typings.json

README.md

ByContract

NPM Build Status Join the chat at https://gitter.im/dsheiko/bycontract

byContract is a small validation library (1,1 KB gzip) that allows you to benefit from Design by Contract programming in your JavaScript code. The lib uses JSDoc expression for a contract. Therefore you likely already familiar with the syntax. The library is implemented as a UMD-compatible module, so you can use as CommonJs and AMD. Besides, it exposes byContract function globally when window object available, meaning you can still use it in non-modular programming.

Getting Started

Test value against a contract
byContract( value, "JSDOC-EXPRESSION" ); // ok or exception
// or
byContract( value, "JSDOC-EXPRESSION", "text" ); // exception message prefixed with `text`
Test set of values against a contract list
byContract( [ value, value ], [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] );  // ok or exception
// e.g.
byContract( arguments, [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] );  // ok or exception
Validate value against a contract
byContract.validate( value, "JSDOC-EXPRESSION" );  // true or false
Usage example: ensure the contract
/**
 * @param {number|string} sum
 * @param {Object.<string, string>} payload
 * @param {function} cb
 * @returns {HTMLElement}
 */
function foo( sum, payload, cb ) {
  // Test if the contract is respected at entry point
  byContract( arguments, [ "number|string", "Object.<string, string>", "function" ] );
  // ..
  var res = document.createElement( "div" );
  // Test if the contract is respected at exit point
  return byContract( res, HTMLElement );
}
// Test it
foo( 100, { foo: "foo" }, function(){}); // ok
foo( 100, { foo: 100 }, function(){}); // exception - ByContractError: Value of index 1 violates the contract `Object.<string, string>`
Usage example: validate the value
class MyModel extends Backbone.Model {
  validate( attrs ) {
    var errors = [];
    if ( !byContract.validate( attrs.id, "!number" ) ) {
      errors.push({ name: "id", message: "Id must be a number" });
    }
    return errors.length > 0 ? errors : false;
  }
}

Contract Expressions

Primitive Types

You can use one of primitive types: array, string, undefined, boolean, function, nan, null, number, object, regexp

byContract( true, "boolean" );
// or
byContract( true, "Boolean" );

Union Types

byContract( 100, "string|number|boolean" ); // ok
byContract( "foo", "string|number|boolean" ); // ok
byContract( true, "string|number|boolean" ); // ok
byContract( [], "string|number|boolean" ); // Exception!

Optional Parameters

function foo( bar, baz ) {
  byContract( arguments, [ "number=", "string=" ] );
}
foo(); // ok
foo( 100 ); // ok
foo( 100, "baz" ); // ok
foo( 100, 100 ); // Exception!
foo( "bar", "baz" ); // Exception!

Array/Object Expressions

byContract( [ 1, 1 ], "Array.<number>" ); // ok
byContract( [ 1, "1" ], "Array.<number>" ); // Exception!

byContract( { foo: "foo", bar: "bar" }, "Object.<string, string>" ); // ok
byContract( { foo: "foo", bar: 100 }, "Object.<string, string>" ); // Exception!

Interface validation

You can validate if a supplied value is an instance of a declared interface:

var MyClass = function(){},
    instance = new MyClass();

byContract( instance, MyClass ); // ok

When the interface is globally available you can set contract as a string:

var instance = new Date();
byContract( instance, "Date" ); // ok
//..
byContract( view, "Backbone.NativeView" ); // ok
//..
byContract( node, "HTMLElement" ); // ok
//..
byContract( ev, "Event" ); // ok

Globally available interfaces can also be used in Array/Object expressions:

byContract( [ new Date(), new Date(), new Date() ], "Array.<Date>" ); // ok

Nullable/Non-nullable Type

byContract( 100, "?number" ); // ok
byContract( null, "?number" ); // ok
byContract( 100, "!number" ); // ok
byContract( null, "!number" ); // Exception!

Validation Exceptions

try {
  byContract( 1, "NaN" );
} catch( err ) {
  console.log( err instanceof Error ); // true
  console.log( err instanceof TypeError ); // true
  console.log( err instanceof byContract.Exception ); // true
  console.log( err.name ); // ByContractError
  console.log( err.message ); // Value violates the contract `NaN`
}
Output in NodeJS
function bar(){
  byContract( 1, "NaN" );
}
function foo() {
  bar();
}

foo();

ByContractError
    at bar (/private/tmp/demo.js:6:3)
    at foo (/private/tmp/demo.js:9:3)
    at Object.<anonymous> (/private/tmp/demo.js:12:1)
    ..

Custom Types

Pretty much like with JSDoc @typedef one can declare a custom type and use it as a contract.

Validating against a Union Type

Here we define a union type for values that can contain either numbers or strings that represent numbers.

byContract.typedef( "NumberLike", "number|string" );
byContract( 10, "NumberLike" ); // OK
byContract( null, "NumberLike" ); // throws Value incorrectly implements interface `NumberLike`

Validating against a Complex Type

This example defines a type Hero that represents an object/namespace required to have properties hasSuperhumanStrength and hasWaterbreathing both of boolean type.

byContract.typedef( "Hero", {
  hasSuperhumanStrength: "boolean",
  hasWaterbreathing: "boolean"
});
var superman = {
  hasSuperhumanStrength: true,
  hasWaterbreathing: false
};
byContract( superman, "Hero" ); // OK

When any of properties violates the specified contract an exception thrown

var superman = {
  hasSuperhumanStrength: 42,
  hasWaterbreathing: null
};
byContract( superman, "Hero" ); // throws Value incorrectly implements interface `Hero`

If value mises a property of the complex type an exception thrown

var auqaman = {
  hasWaterbreathing: true
};
byContract( superman, "Hero" ); // throws Value incorrectly implements interface `Hero`

Custom Validators

Basic type validators exposed publicly in byContract.is namespace. so you can extend it:

byContract.is.email = function( val ){
  var re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
  return re.test( val );
}
byContract( "me@dsheiko.com", "email" ); // ok
byContract( "bla-bla", "email" ); // Exception!

Disable Validation on Production Environment

if ( env === "production" ) {
  byContract.isEnabled = false;
}

Analytics