typeforce
Version:
Another biased type checking solution for Javascript
236 lines (185 loc) • 5.67 kB
JavaScript
var errors = require('./errors')
var native = require('./native')
// short-hand
var tfJSON = errors.tfJSON
var TfTypeError = errors.TfTypeError
var TfPropertyTypeError = errors.TfPropertyTypeError
var tfSubError = errors.tfSubError
var getValueTypeName = errors.getValueTypeName
var types = {
arrayOf: function arrayOf (type) {
type = compile(type)
function _arrayOf (array, strict) {
if (!native.Array(array)) return false
return array.every(function (value, i) {
try {
return typeforce(type, value, strict)
} catch (e) {
throw tfSubError(e, i)
}
})
}
_arrayOf.toJSON = function () { return '[' + tfJSON(type) + ']' }
return _arrayOf
},
eachOf: function eachOf () {
var types = [].slice.call(arguments).map(compile)
function _eachOf (value, strict) {
return types.every(function (type) {
try {
return typeforce(type, value, strict)
} catch (e) {
return false
}
})
}
_eachOf.toJSON = function () { return types.map(tfJSON).join('&') }
return _eachOf
},
maybe: function maybe (type) {
type = compile(type)
function _maybe (value, strict) {
return native.Null(value) || type(value, strict, maybe)
}
_maybe.toJSON = function () { return '?' + tfJSON(type) }
return _maybe
},
map: function map (propertyType, propertyKeyType) {
propertyType = compile(propertyType)
if (propertyKeyType) propertyKeyType = compile(propertyKeyType)
function _map (value, strict) {
if (!native.Object(value, strict)) return false
if (native.Null(value, strict)) return false
for (var propertyName in value) {
try {
if (propertyKeyType) {
typeforce(propertyKeyType, propertyName, strict)
}
} catch (e) {
throw tfSubError(e, propertyName, 'key')
}
try {
var propertyValue = value[propertyName]
typeforce(propertyType, propertyValue, strict)
} catch (e) {
throw tfSubError(e, propertyName)
}
}
return true
}
if (propertyKeyType) {
_map.toJSON = function () {
return '{' + tfJSON(propertyKeyType) + ': ' + tfJSON(propertyType) + '}'
}
} else {
_map.toJSON = function () { return '{' + tfJSON(propertyType) + '}' }
}
return _map
},
object: function object (uncompiled) {
var type = {}
for (var propertyName in uncompiled) {
type[propertyName] = compile(uncompiled[propertyName])
}
function _object (value, strict) {
if (!native.Object(value)) return false
if (native.Null(value)) return false
var propertyName
try {
for (propertyName in type) {
var propertyType = type[propertyName]
var propertyValue = value[propertyName]
typeforce(propertyType, propertyValue, strict)
}
} catch (e) {
throw tfSubError(e, propertyName)
}
if (strict) {
for (propertyName in value) {
if (type[propertyName]) continue
throw new TfPropertyTypeError(undefined, propertyName)
}
}
return true
}
_object.toJSON = function () { return tfJSON(type) }
return _object
},
oneOf: function oneOf () {
var types = [].slice.call(arguments).map(compile)
function _oneOf (value, strict) {
return types.some(function (type) {
try {
return typeforce(type, value, strict)
} catch (e) {
return false
}
})
}
_oneOf.toJSON = function () { return types.map(tfJSON).join('|') }
return _oneOf
},
quacksLike: function quacksLike (type) {
function _quacksLike (value) {
return type === getValueTypeName(value)
}
_quacksLike.toJSON = function () { return type }
return _quacksLike
},
tuple: function tuple () {
var types = [].slice.call(arguments).map(compile)
function _tuple (values, strict) {
return types.every(function (type, i) {
try {
return typeforce(type, values[i], strict)
} catch (e) {
throw tfSubError(e, i)
}
}) && (!strict || values.length === arguments.length)
}
_tuple.toJSON = function () { return '(' + types.map(tfJSON).join(', ') + ')' }
return _tuple
},
value: function value (expected) {
function _value (actual) {
return actual === expected
}
_value.toJSON = function () { return expected }
return _value
}
}
function compile (type) {
if (native.String(type)) {
if (type[0] === '?') return types.maybe(compile(type.slice(1)))
return native[type] || types.quacksLike(type)
} else if (type && native.Object(type)) {
if (native.Array(type)) return types.arrayOf(compile(type[0]))
return types.object(type)
} else if (native.Function(type)) {
return type
}
return types.value(type)
}
function typeforce (type, value, strict, surrogate) {
if (native.Function(type)) {
if (type(value, strict)) return true
throw new TfTypeError(surrogate || type, value)
}
// JIT
return typeforce(compile(type), value, strict)
}
// assign types to typeforce function
for (var typeName in native) {
typeforce[typeName] = native[typeName]
}
for (typeName in types) {
typeforce[typeName] = types[typeName]
}
var extra = require('./extra')
for (typeName in extra) {
typeforce[typeName] = extra[typeName]
}
typeforce.compile = compile
typeforce.TfTypeError = TfTypeError
typeforce.TfPropertyTypeError = TfPropertyTypeError
module.exports = typeforce