Another biased type checking solution for Javascript
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

260 lines
6.5 KiB

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, options) {
type = compile(type)
options = options || {}
function _arrayOf (array, strict) {
if (!NATIVE.Array(array)) return false
if (NATIVE.Nil(array)) return false
if (options.minLength !== undefined && array.length < options.minLength) return false
if (options.maxLength !== undefined && array.length > options.maxLength) return false
if (options.length !== undefined && array.length !== options.length) return false
return array.every(function (value, i) {
try {
return typeforce(type, value, strict)
} catch (e) {
throw tfSubError(e, i)
}
})
}
_arrayOf.toJSON = function () {
var str = '[' + tfJSON(type) + ']'
if (options.length !== undefined) {
str += '{' + options.length + '}'
} else if (options.minLength !== undefined || options.maxLength !== undefined) {
str += '{' +
(options.minLength === undefined ? 0 : options.minLength) + ',' +
(options.maxLength === undefined ? Infinity : options.maxLength) + '}'
}
return str
}
return _arrayOf
},
maybe: function maybe (type) {
type = compile(type)
function _maybe (value, strict) {
return NATIVE.Nil(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)) return false
if (NATIVE.Nil(value)) 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 typePropertyName in uncompiled) {
type[typePropertyName] = compile(uncompiled[typePropertyName])
}
function _object (value, strict) {
if (!NATIVE.Object(value)) return false
if (NATIVE.Nil(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
},
anyOf: function anyOf () {
var types = [].slice.call(arguments).map(compile)
function _anyOf (value, strict) {
return types.some(function (type) {
try {
return typeforce(type, value, strict)
} catch (e) {
return false
}
})
}
_anyOf.toJSON = function () { return types.map(tfJSON).join('|') }
return _anyOf
},
allOf: function allOf () {
var types = [].slice.call(arguments).map(compile)
function _allOf (value, strict) {
return types.every(function (type) {
try {
return typeforce(type, value, strict)
} catch (e) {
return false
}
})
}
_allOf.toJSON = function () { return types.map(tfJSON).join(' & ') }
return _allOf
},
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) {
if (NATIVE.Nil(values)) return false
if (NATIVE.Nil(values.length)) return false
if (strict && (values.length !== types.length)) return false
return types.every(function (type, i) {
try {
return typeforce(type, values[i], strict)
} catch (e) {
throw tfSubError(e, i)
}
})
}
_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
}
}
// TODO: deprecate
TYPES.oneOf = TYPES.anyOf
function compile (type) {
if (NATIVE.String(type)) {
if (type[0] === '?') return TYPES.maybe(type.slice(1))
return NATIVE[type] || TYPES.quacksLike(type)
} else if (type && NATIVE.Object(type)) {
if (NATIVE.Array(type)) {
if (type.length !== 1) throw new TypeError('Expected compile() parameter of type Array of length 1')
return TYPES.arrayOf(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.Buffer(type) && NATIVE.Function(type)) {
if (type(value, strict) || !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