node package manager

bycontract

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