typesafe v0.9.8
Typesafe.js
This JavaScript library provides the ability to perform automatic runtime type checking on function parameters and function polymorphism.
Documentation
Run grunt docs
inside the project root directory to generate API docs for all properties of the ts
object.
Table of contents
- ts.func - Generate functions which check their parameter types
- ts.poly - Function polymorphism for JavaScript!
- Running as a browser library
- Building
- Compatibility
- Tests
- License
ts.func
The ts.func
method will wrap the function passed to it in a TypeSafeFunction
that will check the type of its parameters before running the passed in method.
Syntax
func(params, method[, context, methodName]) → {TypeSafeFunction}
Parameters
params
: An object or array containing the type definitions of each parameter that will be type checked (in the order they appear in the method).- Array notation:
[ <Type Definition>, ... ]
- Object notation:
{ <Parameter Name>: <Type Definition>, ... }
- See below for defining type definitions
- Array notation:
method
: The method whose parameter types should be checked with the passed in type definitions.context
(Optional): The value to use asthis
when executing the method.methodName
(Optional): The method name to display in any invalid type error messages that are thrown.
Returns
A TypeSafeFunction
that will check the types of its parameters before executing method
.
Example
var myFunction = ts.func({
param1: ts.isString
}, function(param1) {
console.log("I ran!");
}, null, 'myFunctionName');
myFunction('a');
> 'I ran!'
myFunction(1);
> 'TypeDefinitionError: [myFunctionName] Error: Object "param1" with value: 1 has an invalid type. Please check the "typeDefinition" property to check valid types and attributes.'
Returning errors instead of throwing them
Normally when an invalid type is passed into a TypeSafeFunction
the function will throw a TypeDefinitionError
stating the parameter name/index of the parameter that did not meet its type definition, the type definition the parameter represents and the value of the parameter. To return a TypeDefinitionError
instead of throwing one, set the throwable
property of the TypeSafeFunction
to false
. e.g.
var myFunction = ts.func([ts.isString], function(param1) {
...
});
myFunction.throwable = false;
To make all TypeSafeFunction
s returned by func
return the error instead of throwing, set the throwable
property of the ts.func
method to false
, e.g.
ts.func.throwable = false;
This will make all future TypeSafeFunction
s returned by func
not throw (previous TypeSafeFunction
s created before setting the property will perform the default action).
Passing Type Definitions
Types for this library are represented using methods which act as type checkers. A method checking for a particular type will accept a single parameter and return either true
or false
if the value passed in matches with the type it is representing or not (note: truthy and falsey values won't always work!).
The ts
object by default provides many different type checkers to check for primitive types (i.e. ts.isString
, ts.isNumber
, etc.). See the wiki for a full list of type checkers provided by the library.
Single Types
To check for single types, simply pass in a single type checker for the parameters. e.g.
var myFunction = ts.func({
param1: ts.isString
}, function() {
...
});
In the above example, myFunction
will only execute the method passed in if the first parameter, param1
, is a string.
Multiple Types
To allow parameters to be one of multiple types, pass in multiple type checkers in an array for the parameter. e.g.
var myFunction = ts.func({
param1: [ts.isString, ts.isInstanceOf(MyCustomClass)]
}, function() {
...
});
In the above example, myFunction
will only execute the method passed in if the first parameter, param1
, is either a string or an instance of the MyCustomClass
class.
Types inside arrays
To check for the type of values inside an array, nest the type checkers inside an array, e.g.
var myFunction = ts.func({
param1: [[ts.isString], [ts.isNumber]]
}, function() {
...
});
In the above example, myFunction
will only execute the method passed in if the first parameter, param1
, is either an array of strings or an array of numbers. To allow an array containing either strings or numbers, nest both type checkers inside a single array, e.g.
var myFunction = ts.func({
param1: [[ts.isString, ts.isNumber]]
}, function() {
...
});
You can nest the type checkers in as many arrays as you like (for example to check for an array of an array of strings, pass in: [[[ts.isString]]]
), but since this library performs type checking at runtime, excessive nesting will affect performance.
Attributes
Sometimes you want to perform additional checks to a parameter that doesn't involve types. For example if one of your parameters is an array, you might want to ensure that the array is not empty. Passing in a "not empty" type checker would not work since only one type checker has to pass for a value for it to be considered valid.
Attributes allow parameters to pass any additional tests to be considered valid. To pass in an attribute, place the type checkers in a type
property inside an object and attributes in an attr
property inside the same object and pass that object to func instead. e.g.
var myFunction = ts.func({
param1: {
types: [[ts.isString]],
attr: ts.isNot(ts.isEmpty)
}
}, function() {
...
});
In the above example, myFunction
will only execute the method passed in if the first parameter, param1
, is an array of strings that is not empty.
Just like with types, multiple attributes can be passed in inside an array and attributes can be further nested inside arrays if you wish for them to be applied to the elements inside an array.
Saving Types
The examples of types mentioned above ultimately get converted by func
into instances of the ts.TypeDefinition
class. Therefore, instances of that class can be passed in to func
as well. The constructor of the class takes in any of the examples of types mentioned above.
An example use case for this might be if you have a specific type definition that is too long and would like to be applied to multiple methods. An instance of the ts.TypeDefinition
class can be created and passed instead so that the type definition does not have to be re-entered multiple times, e.g.
var myTypeDef = new ts.TypeDefnition({
types: [[ts.isString], [ts.isNumber], ts.isInstanceOf(MyClass)],
attr: ts.isNot(ts.isEmpty)
});
var myFunction1 = ts.func({
param1: myTypeDef
}, function(param1) {
...
});
var myFunction2 = ts.func([myTypeDef], function(param1) {
...
});
The above example creates an instance of a TypeDefinition
called myTypeDef
, which defines a particular type. This type will allow either an array of strings, an array of numbers or any instances of the MyClass
object. The value must also not be empty (note that this attribute applies not only to arrays, but to objects as well).
myTypeDef
is then passed in to ts.func
twice to create two separate functions, myFunction1
and myFunction2
, both of which check to ensure that the first parameter matches the type defined by typeDef
.
In the case of myFunction2
, the array form for passing in type definitions was used, see below for for more details about the differences between the array and object notations.
####Allowing null
and undefined
(for users upgrading from v.0.9.6)
For versions 0.9.6 and below, there were additional allowNull
and allowUndefined
properties that could be passed in as well. To have the same behaviour for versions 0.9.7 and above, pass in the ts.isNull
and ts.isUndefined
type checkers for the parameter. Note that the ts.isObject
type checker returns false
for functions and null
objects.
####Array vs. Object Notation for passing in type definitions
Type definitions for individual parameters can be passed into the func
method as either objects or arrays. The object notation (used in the examples above) is an object where the keys are the parameter names and values the type definitions (as either methods, array of methods, objects or instances of the ts.TypeDefinition
class). An example of the object notation would be:
var myFunction = ts.func({
param1: [[ts.isString]]
}, function(param1) {
...
});
Using the object notation lets func
know the names of the parameters being used in the passed in method and can therefore use them in error messages when an invalid type is passed in.
There is also an array notation to represent type definitions to make creating functions from ts.func
seem less intrusive. The array version is basically all the values of the object in the object notation. The above example represented using array notation would be:
var myFunction = ts.func([[[ts.isString]]], function(param1) {
...
});
The tradeoff with this method is that the error messages will only provide the index of the parameter that did not follow its type definition rather than the name of the parameter.
ts.poly
The ts.poly
method will wrap multiple functions passed to it along with the type definitions for their parameters and will return a PolyFunction
which will run the appropriate function based on the types of the parameters passed in. Basically this method attempts to emulate function polymorphism for JavaScript.
Syntax
poly(<params, method[, context, methodName]>*) → {PolyFunction}
Parameters
params
: An object or array containing the type definitions of each parameter that will be type checked (in the order they appear in the method).- Array notation:
[ <Type Definition>, ... ]
- Object notation:
{ <Parameter Name>: <Type Definition>, ... }
- Array notation:
method
: The method whose parameter types should be checked with the passed in type definitions.context
(Optional): The value to use asthis
when executing the method.methodName
(Optional): The method name to display in any invalid type error messages that are thrown.
The above four parameters can be passed in multiple times for each method, e.g.
var myFunction = ts.poly([ts.isString], method1, context, "name",
[ts.isNumber, method1, context, "name2"],
...);
See above for how to represent type definitions.
Returns
A PolyFunction
that will check the types of its parameters and execute the appropriate method
.
Example
var myFunction = ts.poly({
param1: ts.isString
}, function(param1) {
console.log("String version!");
}, null, null,
{
param1: ts.isNumber
}, function(param1) {
console.log("Number version!");
});
myFunction.add([ts.isArray], function(param1) {
console.log("Array version!");
});
myFunction('a');
> 'String version!'
myFunction(1);
> 'Number version!'
myFunction([]);
> 'Array version!'
myFunction(new Date());
> 'TypeDefinitionError: Error: Object "0" with value: Sat Dec 26 2015 23:04:56 GMT-0500 (EST) has an invalid type. Please check the "typeDefinition" property to check valid types and attributes.'
###Adding additional methods
Although multiple methods can be added when running ts.poly
, additional methods can be added after the PolyFunction
is created by calling the add
function of the returned PolyFunction
. e.g.
var myFunction = ts.poly([ts.isString], function(param1) {
...
});
myFunction.add([ts.isNumber], function(param1) {
...
});
Returning errors instead of throwing them
Normally when an invalid type is passed into a PolyFunction
the function will throw a TypeDefinitionError
stating the parameter name/index of the parameter of its last method that did not meet its type definition, the type definition the parameter represents and the value of the parameter. To return a TypeDefinitionError
instead of throwing one, set the throwable
property of the PolyFunction
to false
. e.g.
var myFunction = ts.poly([ts.isString], function(param1) {
...
});
myFunction.throwable = false;
To make all PolyFunction
s returned by poly
return the error instead of throwing, set the throwable
property of the ts.poly
method to false
, e.g.
ts.poly.throwable = false;
This will make all future PolyFunction
s returned by poly
not throw (previous PolyFunction
s will).
Running as a browser library
The latest versions of the library are be under the dist
folder.
Building
- Run
npm install
inside the root directory - Run
grunt
inside the root directory to build typesafe as a browser library. - The built files will be in the
dist
folder.
Compatibility
Browser compatibility was checked using JavaScript Compatibility Checker.
Desktop
IE | Chrome | Firefox | Opera | Safari |
---|---|---|---|---|
9+ | 5+ | 4+ | 12+ | 5.0+ |
Mobile
Opera Mini | iOS Safari | Android |
---|---|---|
5.0+ | 3.2+ | 4.4.3+ |
Tests
Tests are located in the tests/lib
directory of the project. Run grunt test
to run a linter (JSHint) and all tests.
License
This library is licenced under the Apache License v.2.