Signet API
-
isTypeOf
- should verify against an ad-hoc type
- File location: ./test/signet.test.js
-
function () { function is5(value) { return value === 5; } assert.equal(signet.isTypeOf(is5)(5), true); assert.equal(signet.isTypeOf(is5)(6), false); }
-
sign
- should sign a function
- File location: ./test/signet.test.js
-
function () { var expectedSignature = 'A < B :: A:number, B:number => number'; var signedAdd = signet.sign(expectedSignature, addBuilder()); var expectedTree = parser.parseSignature(expectedSignature); assert.equal(JSON.stringify(signedAdd.signatureTree), JSON.stringify(expectedTree)); assert.equal(signedAdd.signature, expectedSignature); }
- should throw an error if signature contains a bad type
- File location: ./test/signet.test.js
-
function () { var fnUnderTest = signet.sign.bind(null, 'number, foo => bar', addBuilder()); var expectedMessage = "Signature contains invalid types: foo, bar"; assert.throws(fnUnderTest, expectedMessage); }
- should throw an error if signature does not satisfy all declared arguments
- File location: ./test/signet.test.js
-
function () { var fnUnderTest = signet.sign.bind(null, 'number => number', addBuilder()); var expectedMessage = 'Signature declaration too short for function with 2 arguments'; assert.throws(fnUnderTest, expectedMessage); }
- should throw error if signature has no output type
- File location: ./test/signet.test.js
-
function () { var fnUnderTest = signet.sign.bind(null, 'number, number', addBuilder()); var expectedMessage = 'Signature must have both input and output types'; assert.throws(fnUnderTest, expectedMessage); }
- should throw error if signature has multiple output types
- File location: ./test/signet.test.js
-
function () { var fnUnderTest = signet.sign.bind(null, 'number, number => number, number', addBuilder()); var expectedMessage = 'Signature can only have a single output type'; assert.throws(fnUnderTest, expectedMessage); }
-
enforce
-
Core Behaviors
- tuple should produce reliable signatures
- File location: ./test/signet.test.js
-
function () { const expectedSignature = 'tuple<*;*> => *'; const testFn = signet.enforce(expectedSignature, () => null); assert.equal(testFn.signature, expectedSignature); }
- should wrap an enforced function with an appropriate enforcer
- File location: ./test/signet.test.js
-
function () { var originalAdd = addBuilder(); var add = signet.enforce('number, number => number', originalAdd); assert.equal(add.toString(), originalAdd.toString()); }
- should enforce a function with a correct argument count
- File location: ./test/signet.test.js
-
function () { var add = signet.enforce('number, number => number', addBuilder()); var expectedMessage = 'Anonymous expected a value of type number but got 6 of type string'; assert.throws(add.bind(null, 5, '6'), expectedMessage); }
- should enforce a function return value
- File location: ./test/signet.test.js
-
function () { var add = signet.enforce('number, number => number', function (a, b) { (a, b); return true; }); var expectedMessage = 'Anonymous expected a return value of type number but got true of type boolean'; assert.throws(add.bind(null, 3, 4), expectedMessage); }
- should return result from enforced function
- File location: ./test/signet.test.js
-
function () { var add = signet.enforce('number, number => number', addBuilder()); assert.equal(add(3, 4), 7); }
- should not throw on unfulfilled optional int argument in a higher-order function containing a variant type
- File location: ./test/signet.test.js
-
function () { function slice(start, end) { (end) } var enforcedSlice = signet.enforce('int, [int] => *', slice); assert.doesNotThrow(function () { enforcedSlice(5); }); }
- should enforce a curried function properly
- File location: ./test/signet.test.js
-
function () { function add(a) { return function (b) { (a, b); return 'bar'; } } var curriedAdd = signet.enforce('number => number => number', add); assert.throws(curriedAdd.bind(null, 'foo')); assert.throws(curriedAdd(5).bind(null, 'foo')); assert.throws(curriedAdd(5).bind(null, 6)); }
-
Custom Errors
- should throw a custom error on bad input
- File location: ./test/signet.test.js
-
function () { var add = signet.enforce('number, number => number', function (a, b) { (a, b); return true; }, { inputErrorBuilder: function (validationResult, args, signatureTree) { return 'This is a custom input error!' + validationResult.toString() + args.toString() + signatureTree.toString(); } }); var expectedMessage = 'This is a custom input error!number,no3,no[object Object],[object Object],[object Object]'; assert.throws(add.bind(null, 3, 'no'), expectedMessage); }
- should throw a default error on bad input with core builder
- File location: ./test/signet.test.js
-
function () { var add = signet.enforce('number, number => number', function (a, b) { (a, b); return true; }, { inputErrorBuilder: function (validationResult, args, signatureTree, functionName) { return signet.buildInputErrorMessage(validationResult, args, signatureTree, functionName); } }); var expectedMessage = 'Anonymous expected a value of type number but got no of type string'; assert.throws(add.bind(null, 3, 'no'), expectedMessage); }
- should throw a default error on bad output with core builder
- File location: ./test/signet.test.js
-
function () { var add = signet.enforce('number, number => number', function (a, b) { (a, b); return true; }, { outputErrorBuilder: function (validationResult, args, signatureTree, functionName) { return signet.buildOutputErrorMessage(validationResult, args, signatureTree, functionName); } }); var expectedMessage = 'Anonymous expected a return value of type number but got true of type boolean'; assert.throws(add.bind(null, 3, 4), expectedMessage); }
- should throw a custom error on bad output
- File location: ./test/signet.test.js
-
function () { var add = signet.enforce('number, number => number', function (a, b) { (a, b); return true; }, { outputErrorBuilder: function (validationResult, args, signatureTree) { return 'This is a custom output error!' + validationResult.toString() + args.toString() + signatureTree.toString(); } }); var expectedMessage = 'This is a custom output error!number,true3,4[object Object],[object Object],[object Object]'; assert.throws(add.bind(null, 3, 4), expectedMessage); }
-
Dependent Type Operator Support
- should properly check symbolic dependent types
- File location: ./test/signet.test.js
-
function () { function orderedProperly(a, b) { return a > b; } var enforcedFn = signet.enforce('A > B :: A:number, B:number => boolean', orderedProperly); function testWith(a, b) { return function () { return enforcedFn(a, b); }; } assert.throws(testWith(5, 6), 'orderedProperly expected a value of type A > B but got A = 5 and B = 6 of type string'); assert.equal(testWith(7, 3)(), true); }
- should properly check symbolic type dependencies
- File location: ./test/signet.test.js
-
function () { function testFnFactory() { return function (a, b) { a + b; return a; }; } assert.throws(signet.enforce( 'A <: B :: A:variant<string;number>, B:variant<string;int> => number', testFnFactory()).bind(null, 2.2, 3), 'Anonymous expected a value of type A <: B but got A = 2.2 and B = 3 of type string'); assert.throws(signet.enforce( 'A < B, B > C :: A:int, B:int, C:int => number', testFnFactory()).bind(null, 5, 6, 7), 'Anonymous expected a value of type B > C but got B = 6 and C = 7 of type string'); assert.doesNotThrow(signet.enforce( 'A <: B :: A:variant<string;int>, B:variant<string;number> => number', testFnFactory()).bind(null, 5, 6)); assert.doesNotThrow(signet.enforce( 'A < B, B < C :: A:int, B:int, C:int => number', testFnFactory()).bind(null, 5, 6, 7)); }
-
Object and Constructor Support
- should properly enforce constructors
- File location: ./test/signet.test.js
-
function () { var testMethodSpy = sinon.spy(); var MyObj = signet.enforce( 'a:int, b:string => undefined', function (a, b) { this.testMethod(a, b); } ); MyObj.prototype.testMethod = testMethodSpy; new MyObj(5, 'foo'); var result = JSON.stringify(testMethodSpy.args[0]); var expectedResult = JSON.stringify([5, 'foo']); assert.equal(testMethodSpy.callCount, 1); assert.equal(result, expectedResult); assert.throws( function () { return new MyObj('foo', 5); }, 'Anonymous expected a value of type a:int but got foo of type string' ); }
- should properly enforce object methods
- File location: ./test/signet.test.js
-
function () { function MyObj(a) { this.a = a; } MyObj.prototype = { testMethod: signet.enforce( 'b:int => result:int', function (b) { return this.a + b; } ) } var objInstance = new MyObj(6); assert.equal(objInstance.testMethod(7), 13); assert.throws( objInstance.testMethod.bind(objInstance, '7'), 'Anonymous expected a value of type b:int but got 7 of type string' ); }
-
Function Properties
- should preserve properties on enforced function
- File location: ./test/signet.test.js
-
function () { function adder(a, b) { return a + b; } adder.myProp = () => 'yay!'; const add = signet.enforce( 'number, number => number', adder ); assert.equal(add.myProp(), 'yay!'); }
- should return a function with the correct arity
- File location: ./test/signet.test.js
-
function () { const add = signet.enforce( 'a:*, b:* => *', function add(a, b) { return a + b; } ); assert.equal(add.length, 2); }
-
Higher-order Function Support
- should support a cross-execution environment table
- File location: ./test/signet.test.js
-
function () { const addIncreasing = signet.enforce( 'a < b, b < sum :: a:int => b:int => sum:int', a => b => a - b ); assert.throws(addIncreasing(5).bind(null, 4), 'Anonymous expected a value of type a < b but got a = 5 and b = 4 of type string'); assert.throws(addIncreasing(5).bind(null, 6), 'Anonymous expected a return value of type b < sum but got b = 6 and sum = -1 of type string'); }
- should enforce passed functions when a signature is provided
- File location: ./test/signet.test.js
-
function () { const testFn = signet.enforce( 'function<* => boolean> => * => boolean', function (fn) { return () => fn(); }); function badFn() { return 'foo'; } assert.throws(testFn(badFn), 'badFn expected a return value of type boolean but got foo of type string'); }
- should should pass options along to sub-enforcement
- File location: ./test/signet.test.js
-
function () { const options = { outputErrorBuilder: function (validationResult, args, signatureTree) { return 'This is a custom output error!' + validationResult.toString() + args.toString() + signatureTree.toString(); } }; const testFn = signet.enforce( 'function<* => boolean> => * => string', function (fn) { return () => fn(); }, options); function badFn() { return 'foo'; } assert.throws(testFn(badFn), 'This is a custom output error!boolean,foo[object Object],[object Object]'); }
- should not throw when function type is declared with constructor argument
- File location: ./test/signet.test.js
-
function () { function doStuff() { return []; } signet.enforce('function<*, * => string, boolean => boolean> => array', doStuff); assert.doesNotThrow(doStuff.bind(null, function () { })); }
-
-
extend
- should register a new type
- File location: ./test/signet.test.js
-
function () { signet.extend('foo', function (value) { return value === 'foo'; }); assert.equal(signet.isType('foo'), true); assert.equal(signet.isTypeOf('foo')('foo'), true); }
- should handle type arity up front
- File location: ./test/signet.test.js
-
function () { signet.extend('myTestType0', function () { }); signet.extend('myTestType1{1}', function () { }); signet.extend('myTestType1OrMore{1,}', function () { }); signet.extend('myTestType2To5{2, 5}', function () { }); assert.doesNotThrow(signet.isTypeOf.bind(null, 'myTestType0<1, 2, 3>')); assert.throws( signet.isTypeOf('myTestType1<1, 2, 3>').bind(null, 'foo'), 'Type myTestType1 accepts, at most, 1 arguments'); assert.throws( signet.isTypeOf('myTestType1').bind(null, 'foo'), 'Type myTestType1 requires, at least, 1 arguments'); assert.doesNotThrow(signet.isTypeOf('myTestType1OrMore<1, 2, 3>').bind(null, 'foo')); assert.throws( signet.isTypeOf('myTestType1OrMore').bind(null, 'foo'), 'Type myTestType1OrMore requires, at least, 1 arguments'); assert.doesNotThrow(signet.isTypeOf('myTestType2To5<1, 2, 3>').bind(null, 'foo')); assert.throws( signet.isTypeOf('myTestType2To5').bind(null, 'foo'), 'Type myTestType2To5 requires, at least, 2 arguments'); assert.throws( signet.isTypeOf('myTestType2To5<1, 2, 3, 4, 5, 6>').bind(null, 'foo'), 'Type myTestType2To5 accepts, at most, 5 arguments'); assert.throws( signet.extend.bind(null, 'myTestTypeBroken{5, 1}', function () { }), 'Error in myTestTypeBroken arity declaration: min cannot be greater than max'); }
-
subtype
- should register a subtype
- File location: ./test/signet.test.js
-
function () { signet.subtype('number')('intFoo', function (value) { return Math.floor(value) === value; }); assert.equal(signet.isSubtypeOf('number')('intFoo'), true); assert.equal(signet.isTypeOf('intFoo')(15), true); }
-
alias
- should allow aliasing of types by other names
- File location: ./test/signet.test.js
-
function () { signet.alias('foo', 'string'); assert.equal(signet.isTypeOf('foo')('bar'), true); assert.equal(signet.isTypeOf('foo')(5), false); }
- should partially apply a type value
- File location: ./test/signet.test.js
-
function () { signet.alias('testTuple', 'tuple<_; _>'); signet.alias('testPartialTuple', 'testTuple<int; _>'); assert.equal(signet.isTypeOf('testTuple<array; object>')([[], {}]), true); assert.equal(signet.isTypeOf('testPartialTuple<string>')([5, 'foo']), true); assert.equal(signet.isTypeOf('testPartialTuple<string>')([5, 6]), false); }
-
verify
- should allow function argument verification inside a function body
- File location: ./test/signet.test.js
-
function () { function test(a, b) { (a, b); signet.verify(test, arguments); } signet.sign('string, number => undefined', test); assert.throws(test.bind(5, 'five')); }
-
typeChain
- should return correct type chains
- File location: ./test/signet.test.js
-
function () { const arrayTypeChain = signet.typeChain('array'); const numberTypeChain = signet.typeChain('number'); assert.equal(arrayTypeChain, '* -> object -> array'); assert.equal(numberTypeChain, '* -> number'); }
-
duckTypeFactory
- should duck type check an object
- File location: ./test/signet.test.js
-
function () { var isMyObj = signet.duckTypeFactory({ foo: 'string', bar: 'int', baz: 'array' }); signet.subtype('object')('myObj', isMyObj); assert.equal(signet.isTypeOf('myObj')({ foo: 55 }), false); assert.equal(signet.isTypeOf('myObj')({ foo: 'blah', bar: 55, baz: [] }), true); }
- should return false if value is not duck-type verifiable
- File location: ./test/signet.test.js
-
function () { var isMyObj = signet.duckTypeFactory({ foo: 'string', bar: 'int', baz: 'array' }); assert.equal(isMyObj(null), false); }
-
exactDuckTypeFactory
- should check and exact duck type on an object
- File location: ./test/signet.test.js
-
function () { var isMyObj = signet.exactDuckTypeFactory({ foo: 'string', bar: 'int', baz: 'array' }); signet.subtype('object')('myExactObj', isMyObj); assert.equal(signet.isTypeOf('myExactObj')({ foo: 'blah', bar: 55, baz: [], quux: '' }), false); assert.equal(signet.isTypeOf('myExactObj')({ foo: 'blah', bar: 55, baz: [] }), true); }
- should check and exact duck type on an object
- File location: ./test/signet.test.js
-
function () { var isMyObj = signet.exactDuckTypeFactory({ foo: 'string', bar: 'int', baz: 'array' }); signet.subtype('object')('myExactObj', isMyObj); assert.equal(signet.isTypeOf('myExactObj')({ foo: 'blah', bar: 55, baz: [], quux: '' }), false); assert.equal(signet.isTypeOf('myExactObj')({ foo: 'blah', bar: 55, baz: [] }), true); }
-
defineDuckType
- should allow duck types to be defined directly
- File location: ./test/signet.test.js
-
function () { signet.defineDuckType('myObj', { foo: 'string', bar: 'int', baz: 'array' }); assert.equal(signet.isTypeOf('myObj')({ foo: 55 }), false); assert.equal(signet.isTypeOf('myObj')({ foo: 'blah', bar: 55, baz: [] }), true); }
- should allow reporting of duck type errors
- File location: ./test/signet.test.js
-
function () { signet.defineDuckType('aTestThingy', { quux: '!*' }); signet.defineDuckType('myObj', { foo: 'string', bar: 'int', baz: 'array', deeperType: 'aTestThingy' }); var result = signet.reportDuckTypeErrors('myObj')({ foo: 55, bar: 'bad value', baz: null, deeperType: {} }); var expected = '[["foo","string",55],["bar","int","bad value"],["baz","array",null],["deeperType","aTestThingy",[["quux","not<variant<undefined, null>>",null]]]]'; assert.equal(JSON.stringify(result), expected); assert.equal(signet.isTypeOf('myObj')({ foo: 'blah', bar: 55, baz: [], deeperType: { quux: 'something' } }), true); }
-
reportDuckTypeErrors
- should return duck type error on bad object value
- File location: ./test/signet.test.js
-
function () { signet.defineDuckType('duckTest', {}); let checkDuckTest = signet.reportDuckTypeErrors('duckTest'); const nullCheck = checkDuckTest(null); const intCheck = checkDuckTest(55); const stringCheck = checkDuckTest('foo'); assert.equal(JSON.stringify(nullCheck), '[["badDuckTypeValue","object",null]]'); assert.equal(JSON.stringify(intCheck), '[["badDuckTypeValue","object",55]]'); assert.equal(JSON.stringify(stringCheck), '[["badDuckTypeValue","object","foo"]]'); }
-
isRegisteredDuckType
- should allow querying of registered duck types
- File location: ./test/signet.test.js
-
function () { signet.defineDuckType('duckFoo', {}); assert.equal(signet.isRegisteredDuckType('duckFoo'), true); assert.equal(signet.isRegisteredDuckType('duckBar'), false); }
-
whichVariantType
- should get variant type of value
- File location: ./test/signet.test.js
-
function () { var getValueType = signet.whichVariantType('variant<string; int>'); assert.equal(getValueType('foo'), 'string'); assert.equal(getValueType(17), 'int'); assert.equal(getValueType(17.5), null); }
-
verifyValueType
- should return value when it matches type correctly
- File location: ./test/signet.test.js
-
function () { var stringValue = 'foo'; var stringResult = signet.verifyValueType('string')(stringValue); var boundedIntValue = 5; var boundedIntResult = signet.verifyValueType('leftBoundedInt<4>')(boundedIntValue); assert.equal(stringResult, stringValue); assert.equal(boundedIntResult, boundedIntValue); }
- should throw an error if the value is of incorrect type
- File location: ./test/signet.test.js
-
function () { var verifyStringValue = signet.verifyValueType('string'); var verifyBoundedIntValue = signet.verifyValueType('leftBoundedInt<4>'); assert.throws(() => verifyStringValue({})); assert.throws(() => verifyBoundedIntValue(-3)); }
-
iterateOn and recursiveTypeFactory
- should allow easy creation of a recursive type
- File location: ./test/signet.test.js
-
function () { const isListNode = signet.duckTypeFactory({ value: 'int', next: 'composite<not<array>, object>' }); const iterableFactory = signet.iterateOn('next'); const isIntList = signet.recursiveTypeFactory(iterableFactory, isListNode); const testList = cons(1, cons(2, cons(3, cons(4, cons(5, null))))); assert.equal(isIntList(testList), true); assert.equal(isIntList({ value: 1 }), false); assert.equal(isIntList('blerg'), false); }
- should properly recurse through a binary tree with left and right values
- File location: ./test/signet.test.js
-
function () { const isBinaryTreeNode = signet.duckTypeFactory({ value: 'int', left: 'composite<^array, object>', right: 'composite<^array, object>', }); function isOrderedNode(node) { return isBinaryTreeNode(node) && ((node.left === null || node.right === null) || (node.value > node.left.value && node.value <= node.right.value)); } signet.subtype('object')('orderedBinaryTreeNode', isOrderedNode); function iteratorFactory(value) { var iterable = []; iterable = value.left !== null ? iterable.concat([value.left]) : iterable; iterable = value.right !== null ? iterable.concat([value.right]) : iterable; return signet.iterateOnArray(iterable); } signet.defineRecursiveType('orderedBinaryTree', iteratorFactory, 'orderedBinaryTreeNode'); const isOrderedIntTree = signet.isTypeOf('orderedBinaryTree'); const goodBinaryTree = { value: 0, left: { value: -1, left: null, right: null }, right: { value: 1, left: { value: 1, left: null, right: null }, right: null } }; const badTree = { value: 0, left: { value: -1, left: null, right: null }, right: { value: -3, left: { value: 1, left: null, right: null }, right: null } }; const malformedTree = { value: 0, left: null }; assert.equal(isOrderedIntTree(goodBinaryTree), true); assert.equal(isOrderedIntTree(badTree), false); assert.equal(isOrderedIntTree(malformedTree), false); }