项目作者: dsheiko

项目描述 :
Argument validation library based on JSDOC syntax
高级语言: JavaScript
项目地址: git://github.com/dsheiko/bycontract.git
创建时间: 2016-01-08T17:41:13Z
项目社区:https://github.com/dsheiko/bycontract

开源协议:

下载


ByContract 2

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

bycontract – A lightweight runtime type checker for JavaScript and TypeScript, powered by JSDOC syntax. Ensure your functions receive the right arguments with simple, declarative contract definitions. No transpilation needed—just clean, reliable validation.

Highlights

  • JSDoc-based validation – Use familiar JSDoc expressions for defining type contracts.
  • Comprehensive contract checks – Validate both function inputs and return values.
  • Clear, actionable errors – Get detailed exceptions, inspired by aproba.
  • Deep object validation – Support for recursive structure checks.
  • Interface validation – Ensure objects conform to expected shapes.
  • Flexible usage – Available as a template tag and property decorators.
  • Production-ready – Can be disabled or stripped out entirely for optimized builds.

Table of contents

Welcome ByContract

Main flavor

  1. function pdf( path, w, h, options, callback ) {
  2. validate( arguments, [
  3. "string",
  4. "!number",
  5. "!number",
  6. PdfOptionsType,
  7. "function=" ] );
  8. }

Template tag flavor

  1. function pdf( path, w, h, options, callback ) {
  2. validateContract`
  3. {string} ${ path }
  4. {!number} ${ w }
  5. {!number} ${ h }
  6. {#PdfOptionsType} ${ options }
  7. {function=} ${ callback }
  8. `;
  9. }

Property decorator flavor

  1. class Page {
  2. @validateJsdoc(`
  3. @param {string} path
  4. @param {!number} w
  5. @param {!number} h
  6. @param {#PdfOptionsType} options
  7. @param {function=} callback
  8. @returns {Promise}
  9. `)
  10. pdf( path, w, h, options, callback ) {
  11. return Promise.resolve();
  12. }
  13. }

Where to use it

Node.js

  1. npm install bycontract
  1. const { validate } = require( "bycontract" );
  2. validate( 1, "number|string" );

Browser

  1. <script src="dist/byContract.min.js"></script>
  2. <script>
  3. const { validate } = byContract;
  4. validate( 1, "number|string" );
  5. </script>

ES6 Module / Webpack

  1. npm install bycontract
  1. import { validate } from "bycontract";
  2. validate( 1, "number|string" );

Syntax Overview

Main flavor

Validate arguments
  1. validate( arguments, [ "JSDOC-EXPRESSION", "JSDOC-EXPRESSION" ] ); // ok or exception
Validate a single value (e.g. return value)
  1. validate( value, "JSDOC-EXPRESSION" ); // ok or exception
Example
  1. import { validate } from "bycontract";
  2. const PdfOptionsType = {
  3. scale: "?number"
  4. }
  5. /**
  6. * Example
  7. * @param {string} path
  8. * @param {!number} w
  9. * @param {!number} h
  10. * @param {PdfOptionsType} options
  11. * @param {function=} callback
  12. */
  13. function pdf( path, w, h, options, callback ) {
  14. validate( arguments, [
  15. "string",
  16. "!number",
  17. "!number",
  18. PdfOptionsType,
  19. "function=" ] );
  20. //...
  21. const returnValue = Promise.resolve();
  22. return validate( returnValue, "Promise" );
  23. }
  24. pdf( "/tmp/test.pdf", 1, 1, { scale: 1 } );
  25. // Test it
  26. pdf( "/tmp/test.pdf", "1", 1, { scale: 1 } ); // ByContractError: Argument #1: expected non-nullable but got string

Template Tag flavor

  1. validateContract`
  2. {JSDOC-EXPRESSION} ${ var1 }
  3. {JSDOC-EXPRESSION} ${ var2 }
  4. `;
Example
  1. import { validate, typedef } from "bycontract";
  2. typedef("#PdfOptionsType", {
  3. scale: "number"
  4. });
  5. function pdf( path, w, h, options, callback ) {
  6. validateContract`
  7. {string} ${ path }
  8. {!number} ${ w }
  9. {!number} ${ h }
  10. {#PdfOptionsType} ${ options }
  11. {function=} ${ callback }
  12. `;
  13. }

or you can copy/paste from JSDoc:

  1. function pdf( path, w, h, options, callback ) {
  2. validateContract`
  3. @param {string} ${ path }
  4. @param {!number} ${ w }
  5. @param {!number} ${ h }
  6. @param {#PdfOptionsType} ${ options }
  7. @param {function=} ${ callback }
  8. `;
  9. }

Property Decorator flavor

  1. @validateJsdoc`
  2. @param {JSDOC-EXPRESSION} param1
  3. @param {JSDOC-EXPRESSION} param2
  4. `;
Example
  1. import { validate, typedef } from "bycontract";
  2. typedef("#PdfOptionsType", {
  3. scale: "number"
  4. });
  5. class Page {
  6. @validateJsdoc(`
  7. @param {string} path
  8. @param {!number} w
  9. @param {!number} h
  10. @param {#PdfOptionsType} options
  11. @param {function=} callback
  12. @returns {Promise}
  13. `)
  14. pdf( path, w, h, options, callback ) {
  15. return Promise.resolve();
  16. }
  17. }
  1. const page = new Page();
  2. page.pdf( "/tmp/test.pdf", "1", 1, { scale: 1 } );
  3. // ByContractError:
  4. // Method: pdf, parameter w: expected non-nullable but got string

This solution requires legacy decorators proposal support. You can get it with following Babel configuration

  1. {
  2. presets: [
  3. [ "@babel/preset-env" ]
  4. ],
  5. plugins: [
  6. [ "@babel/plugin-proposal-decorators", { "legacy": true } ]
  7. ]
  8. }

Types

Primitive Types

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

  1. validate( true, "boolean" );
  2. // or
  3. validate( true, "Boolean" );
  1. validate( null, "boolean" ); // ByContractError: expected boolean but got null
  2. const fn = () => validate( arguments, [ "boolean", "*" ]);
  3. fn( null, "any" ); // ByContractError: Argument #0: expected boolean but got null

Union Types

  1. validate( 100, "string|number|boolean" ); // ok
  2. validate( "foo", "string|number|boolean" ); // ok
  3. validate( true, "string|number|boolean" ); // ok
  4. validate( [], "string|number|boolean" );
  5. // ByContractError: expected string|number|boolean but failed on each:
  6. // expected string but got array, expected number but got array, expected boolean but got array

Optional Parameters

  1. function foo( bar, baz ) {
  2. validate( arguments, [ "number=", "string=" ] );
  3. }
  4. foo(); // ok
  5. foo( 100 ); // ok
  6. foo( 100, "baz" ); // ok
  7. foo( 100, 100 ); // ByContractError: Argument #1: expected string but got number
  8. foo( "bar", "baz" ); // ByContractError: Argument #0: expected number but got string

Array Expression

  1. validate( [ 1, 1 ], "Array.<number>" ); // ok
  2. validate( [ 1, "1" ], "Array.<number>" );
  3. // ByContractError: array element 1: expected number but got string
  4. // or
  5. validate( [ 1, 1 ], "number[]" ); // ok
  6. validate( [ 1, "1" ], "number[]" );
  7. // ByContractError: array element 1: expected number but got string

Object Expression

  1. validate( { foo: "foo", bar: "bar" }, "Object.<string, string>" ); // ok
  2. validate( { foo: "foo", bar: 100 }, "Object.<string, string>" );
  3. // ByContractError: object property bar: expected string but got number

Structure

  1. validate({
  2. foo: "foo",
  3. bar: 10
  4. }, {
  5. foo: "string",
  6. bar: "number"
  7. }); // ok
  8. validate({
  9. foo: "foo",
  10. bar: {
  11. quiz: [10]
  12. }
  13. }, {
  14. foo: "string",
  15. bar: {
  16. quiz: "number[]"
  17. }
  18. }); // ok
  19. validate({
  20. foo: "foo",
  21. bar: 10
  22. }, {
  23. foo: "string",
  24. bar: "number"
  25. }); // ByContractError: property #bar expected number but got null

Interface validation

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

  1. class MyClass {}
  2. const instance = new MyClass();
  3. validate( instance, MyClass ); // ok
  1. class MyClass {}
  2. class Bar {}
  3. const instance = new MyClass();
  4. validate( instance, Bar );
  5. // ByContractError: expected instance of Bar but got instance of MyClass

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

  1. const instance = new Date();
  2. validate( instance, "Date" ); // ok
  3. //..
  4. validate( node, "HTMLElement" ); // ok
  5. //..
  6. validate( ev, "Event" ); // ok

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

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

Nullable Type

  1. validate( 100, "?number" ); // ok
  2. validate( null, "?number" ); // ok

Validation Exceptions

  1. import { validate, Exception } from "bycontract";
  2. try {
  3. validate( 1, "NaN" );
  4. } catch( err ) {
  5. console.log( err instanceof Error ); // true
  6. console.log( err instanceof TypeError ); // true
  7. console.log( err instanceof Exception ); // true
  8. console.log( err.name ); // ByContractError
  9. console.log( err.message ); // expected nan but got number
  10. }

Combinations

Sometimes we allow function to accept different sequences of types.
Let’s take an example:

  1. function andLogAndFinish( spec, tracker, done ) {
  2. validate( "SOF|SZF|OOF|OZF", [ spec, tracker, done ] )
  3. //...
  4. }

Where the following sequences of types valid:

  • string, object, function
  • string, null, function
  • object, object, function
  • object, null, function
  1. import { validateCombo } from "bycontract";
  2. const CASE1 = [ "string", TRACKER_OPTIONS, "function" ],
  3. CASE2 = [ "string", null, "function" ],
  4. CASE3 = [ SPEC_OPTIONS, TRACKER_OPTIONS, "function" ],
  5. CASE4 = [ SPEC_OPTIONS, null, "function" ];
  6. validateCombo( arguments, [ CASE1, CASE2, CASE3, CASE4 ] );

Function validateCombo throws exception when none of the cases is valid

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.

  1. import { validate, typedef } from "bycontract";
  2. typedef( "NumberLike", "number|string" );
  3. validate( 10, "NumberLike" ); // OK
  4. validate( null, "NumberLike" ); // ByContractError: expected number|string but got null

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.

  1. import { validate, typedef } from "bycontract";
  2. typedef( "#Hero", {
  3. hasSuperhumanStrength: "boolean",
  4. hasWaterbreathing: "boolean"
  5. });
  6. var superman = {
  7. hasSuperhumanStrength: true,
  8. hasWaterbreathing: false
  9. };
  10. validate( superman, "#Hero" ); // OK

When any of properties violates the specified contract an exception thrown

  1. var superman = {
  2. hasSuperhumanStrength: 42,
  3. hasWaterbreathing: null
  4. };
  5. validate( superman, "#Hero" ); // ByContractError: property #hasSuperhumanStrength expected boolean but got number

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

  1. var auqaman = {
  2. hasWaterbreathing: true
  3. };
  4. validate( superman, "#Hero" ); // ByContractError: missing required property #hasSuperhumanStrength

Custom Validators

Basic type validators exposed exported as is object. So you can extend it:

  1. import { validate, is } from "bycontract";
  2. is.email = function( val ){
  3. 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,}))$/;
  4. return re.test( val );
  5. }
  6. validate( "me@dsheiko.com", "email" ); // ok
  7. validate( "bla-bla", "email" ); // ByContractError: expected email but got string

Production Environment

You can disable validation logic for production env like

  1. import { validate, config } from "bycontract";
  2. if ( process.env.NODE_ENV === "production" ) {
  3. config({ enable: false });
  4. }

Alternatively you can fully remove the library from the production codebase with Webpack:

webpack config

  1. const webpack = require( "webpack" ),
  2. TerserPlugin = require( "terser-webpack-plugin" );
  3. module.exports = {
  4. mode: process.env.NODE_ENV || "development",
  5. ...
  6. optimization: {
  7. minimizer: [
  8. new TerserPlugin(),
  9. new webpack.NormalModuleReplacementPlugin(
  10. /dist\/bycontract\.dev\.js/,
  11. ".\/bycontract.prod.js"
  12. )
  13. ]
  14. }
  15. };

building for development

  1. npx NODE_ENV=development webpack

building for production

  1. npx NODE_ENV=production webpack

Analytics