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 }, 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) { 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 } } 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)) 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.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