snug

Make your functions safe.

Usage no npm install needed!

<script type="module">
  import snug from 'https://cdn.skypack.dev/snug';
</script>

README

Snug

Build Status

Make your functions safe at run time. Sometimes your may get data from sources during run time (e.g. the web) where you are not able to take advantage of compile-time type checking. This is for handling checks and pattern matching at run time.

Install

npm install snug

Usage

Type Annotations

Make your input and output values snug. The inputs arrays corresponds to the respective input arguments. Functions are expected, so you can add whatever logic you desire for your type. If you do not specify any inputs or outputs, there will be no checks.

var snug = require('snug');

var sum = snug.annotate({
  inputs: [lodash.isNumber, lodash.isNumber],
  outputs: [lodash.isNumber],
  fn: function(a, b) {
    return a + b;
  }
});

sum(3, 4); // Returns 7
sum(3); // TypeError - Not enough arguments
sum(3, 4, 5); // TypeError - Too many arguments
sum('a', 'b'); // TypeError - wrong types

If you would like to define your annotations apart from your functions, simply leave off the fn property. You will then get a function in which you can pass in a function to annotate.

var snug = require('snug');

var sumAnnotation = snug.annotate({
  inputs: [lodash.isNumber, lodash.isNumber],
  outputs: [lodash.isNumber]
});

var sum = sumAnnotation(function(a, b) {
  return a + b;
});

sum(3, '4'); // TypeError

Catching Errors

Sometimes, you want to make a function fail gracefully. You can do this with the catch function.

var snug = require('snug');

var sum = snug.annotate({
  inputs: [lodash.isNumber, lodash.isNumber],
  outputs: [lodash.isNumber],
  fn: function(a, b) {
    return a + b;
  },
  catch: function(error) {
    return 0;
  }
});

sum('a', 'b'); // TypeError is caught, calls catch function, returns 0

Creating Prototype Chains

Sometimes you may want to create prototype chains. The code gets really ugly when doing that and using annotations. Using the create method will let you build a prototype chain that are annotation objects. Note how the fn functions use this.

var MyFunction = snug.create({
  constructor: {
    fn: function(a) {
      this.a = a;
    }
  },

  add: {
    inputs: [lodash.isNumber],
    outputs: [lodash.isNumber],
    fn: function(b) {
      return this.a + b;
    }
  }
});

var myFn = new MyFunction(1);
myFn.add(2); // Returns 3

Pattern Matching

You can take your annotations and call the first matching function using patternMatch.

var fib = snug.annotate({
  inputs: [lodash.isNumber],
  fn: snug.patternMatch([
    {
      inputs: [function(n) { return n === 0 || n === 1; }],
      fn: function() { return 1; }
    },
    {
      inputs: [function(n) { return n > 0; }],
      fn: function(n) { return fib(n - 2) + fib(n - 1); }
    }
  ])
});

fib(10); // 89
fib(-10); // Error: No pattern match found
fib('10'); // TypeError because it requires a number

You may use .extend to add a catch function for the patternMatch function, which returns an annotation.

Structures

You may want to iterate over an object or array to see if every item passes a certain check. There are two functions to help with this.

Typed Array Structure

This ensures that every item in the array passes a single test.

var check = snug.structures.typedArray(lodash.isNumber);
check([1, 2, 3]); // True
check([1, '2', 3]); // False

Compare Array

You can use this function to compare two arrays.

var check = snug.structures.compareArray([
  lodash.isNumber,
  lodash.isString,
  lodash.isNumber
]);

check([1, '2', 3]); // True
check([1, 2, 3]); // False

Object Structure

This looks at every key in an object and checks to see if each passes the check.

var check = snug.structures.object({
  foo: lodash.isNumber,
  bar: lodash.isBoolean
});

check({
  foo: 4,
  bar: true
}); // True

check({
  foo: 4,
  bar: 'true'
}); // False

check({
  foo: 4
}); // False

Logic

Sometimes, you need to get fancy with your annotations.

and

var check = snug.logic.and([
  lodash.isNumber,
  function(value) { return value < 10; }
]);

check(4); // True
check(100); // False

or

var check = snug.logic.or([
  lodash.isNumber,
  function(value) { return value === 'foobar'; }
]);

check(4); // True
check('foobar'); // True
check('hello world'); // False

nor

var check = snug.logic.nor([
  lodash.isNumber,
  function(value) { return value === 'foobar'; }
]);

check(9); // False
check('foobar'); // False
check('hello world'); // True

not

var check = snug.logic.not(lodash.isArray);

check(9); // True
check([1, 2, 3]); // False

wildcard

Maybe you don't care about the type of a given value. If so, you can use wildcard to say any type is fine.

var check = snug.logic.wildcard();

// No matter what value is given, it will be true
check('anything'); // True

optional

You may have figured out to use the or function with an isUndefined check as the first item to make checks options, but that gets kind of verbose after a while. You can use optional to do a check or pass if the value is undefined.

var check = snug.logic.optional(lodash.isNumber);

check(4); // True
check('4'); // False
check(); // True

equalLengthWith

One test is to ensure two arrays are of equal length.

var check = snug.logic.equalLengthWith([1, 2, 3]);

check([1, 2, 3]); // True
check([1, 2]); // False

equals

Allows for deep testing equality.

var check = snug.logic.equals([1, 2, 3]);

check([1, 2, 3]); // True
check([1, 2]); // False

Access to Config

Sometimes, when you create an annotation, you may want to access the configuration you passed to it. Each function exposes this through a $config property.

This is useful for testing annotations.

var sumAnnotation = snug.annotate({
  inputs: [lodash.isNumber, lodash.isNumber],
  outputs: [lodash.isNumber]
});

sumAnnotation.$config; // This is the object with inputs and outputs provided above

Extending Existing Annotations

If you want an annotation based on an existing annotation, you can use extend.

// This function does not have a catch function
var sum = snug.annotate({
  inputs: [lodash.isNumber, lodash.isNumber],
  outputs: [lodash.isNumber],
  fn: function(a, b) {
    return a + b
  }
});

// This one does, and is a new function
var sumWithCatch = sum.extend({
  catch: function(error) {}
});

sum('a'); // Error is not caught
sumWithCatch('a'); // Error is caught