typing
Version:
A type checking and JSON schema validation library
520 lines (410 loc) • 10.4 kB
JavaScript
// dependencies
//var util = require('util');
// The core structure to store type definitions
var types = {
};
//++++ core functions
// define type
function define(name, definition) {
if (null == name || !is_str(name) || 0 == name.length) {
throw new Error('Invalid type name: ' + name);
}
if (!is_valid_type(definition)) {
throw new Error('Invalid type definition: ' + definition);
}
types[name] = definition;
}
// match value agains the type
function match(type, value) {
}
// check type
function check(type, value) {
//console.log("+check(), type=%s, value=%s", util.inspect(type), util.inspect(value));
// if the <type> has __check__ function, then just use it
if (is_func(type.__check__)) {
return type.__check__(value);
}
else if (is_json(type)) {
// if the <type> is an object, do pattern matching
if (!is_json(value)) {
return false;
}
for (var prop in type) {
var matched = check(type[prop], value[prop]);
if (true != matched) {
return false;
}
}
return true;
}
else if (is_str(type)) {
// if the <type> is a string, it's a type name
if (null != types[type]) {
return check(types[type], value);
}
else {
console.error('Undefined type ' + type);
return false;
}
}
return false;
}
function is_valid_type(def) {
if (is_func(def.__check__)) {
return true;
}
else if (is_str(def)) {
if (name == def || null != types[name]) {
return true;
}
return false;
}
else if (is_json(def)) {
for (var prop in def) {
if (!is_valid_type(def[prop])) {
console.error('Invalid type: %j', def[prop]);
return false;
}
}
return true;
}
return false;
}
// lazy resolving type
function type(name) {
var _type = function() {};
_type.__name__ = name;
_type.__check__ = function(value) {
return (null != types[name]) && check(types[name], value);
};
return _type;
}
//-- core functions
//++++ util functions
function generate_type_name(fn, args) {
var name = fn.name;
if (null != args.__name__) {
name += '(' + args.__name__ + ')';
}
else if (is_str(args)) {
name += '(' + args + ')';
}
else if (is_args(args) || is_array(args)) {
name += '(';
for (var i = 0; i < args.length; ++i) {
if (i > 0) {
name += ',';
}
if (args[i].__name__) {
name += args[i].__name__;
}
else {
name += args[i];
}
}
name += ')';
}
else if (is_json(args)) {
name += '(json)';
}
return name;
}
function type_name(type) {
if (null != type.__name__) {
return type.__name__;
}
if (is_str(type)) {
return type;
}
return null;
}
function is_str(arg) {
return "string" == typeof(arg);
}
function is_json(arg) {
return null != arg && "object" == typeof(arg) && !(arg instanceof Array);
}
function is_array(arg) {
return arg instanceof Array;
}
function is_args(args) {
if (is_json(args) && args.length >= 0) {
for (var i = 0; i < args.length; ++i) {
if (undefined === args[i]) {
return false;
}
}
return true;
}
return false;
}
function is_num(arg) {
return "number" == typeof(arg);
}
function is_int(arg) {
return "number" == typeof(arg) && (0 == arg % 1);
}
function is_float(arg) {
return "number" == typeof(arg) && (0 != arg % 1);
}
function is_func(arg) {
return "function" == typeof(arg);
}
//---- util functions
//++++ type operations
// and
function and() {
var _args = arguments;
var _and = function() {};
_and.__check__ = function(value) {
for (var i in _args) {
if (true != check(_args[i], value)) {
return false;
}
}
return true;
};
return _and;
}
// or
function or() {
var _args = arguments;
var _or = function() {};
_or.__name__ = generate_type_name(or, _args);
_or.__check__ = function(value) {
for (var i in _args) {
if (true == check(_args[i], value)) {
return true;
}
}
return false;
};
return _or;
}
//---- type operations
//++++ built-in types
// any: any type
function any() {
}
any.__name__ = 'any';
any.__check__ = function(value) {
return true;
}
// none: nullable type, extends other types to accept null value
function nullable(type) {
function _nullable() {
}
_nullable.__name__ = generate_type_name(nullable, type);
_nullable.__check__ = function(value) {
return null == value || check(type, value);
};
return _nullable;
}
nullable.__name__ = 'nullable';
nullable.__check__ = function(value) {
return null == value;
};
// str: string type
function str(min_len, max_len) {
var _args = arguments;
var _str = function() {};
_str.__name__ = generate_type_name(str, _args);
_str.__check__ = function(value) {
if (null == value) {
if (min_len > 0) {
return false;
}
return true;
}
if (!is_str(value)) {
return false;
}
if (null != min_len && value.length < min_len) {
return false;
}
if (null != max_len && value.length > max_len) {
return false;
}
return true;
};
return _str;
}
str.__name__ = 'str';
str.__check__ = function(value) {
return null == value || is_str(value)
};
// char: char type
function char() {
}
char.__name__ = 'char';
char.__check__ = str(1,1).__check__;
// bool: bool type
function bool() {
}
bool.__name__ = 'bool';
bool.__check__ = function(value) {
return true === value || false === value;
}
// int: integer type
function int(min, max) {
var _args = arguments;
var _int = function() {};
_int.__name__ = generate_type_name(int, _args);
_int.__check__ = function(value) {
if (null == value) {
return false;
}
if (!is_int(value)) {
return false;
}
if (null != min && value < min) {
return false;
}
if (null != max && value > max) {
return false;
}
return true;
};
return _int;
}
int.__name__ = 'int';
int.__check__ = function(value) {
return null != value && is_int(value);
}
// num: number type
function num(min, max) {
var _args = arguments;
var _num = function() {};
_num.__name__ = generate_type_name(num, _args);
_num.__check__ = function(value) {
if (null == value) {
return false;
}
if (!is_num(value)) {
return false;
}
if (null != min && value < min) {
return false;
}
if (null != max && value > max) {
return false;
}
return true;
};
return _num;
}
num.__name__ = 'num';
num.__check__ = function(value) {
return null != value && is_num(value);
}
// enumeration: enumeration type
function enumeration() {
var _args = arguments;
var _enumeration = function() {};
_enumeration.__name__ = generate_type_name(enumeration, _args);
_enumeration.__check__ = function(value) {
for (var i in _args) {
if (_args[i] === value) {
return true;
}
}
return false;
};
return _enumeration;
}
// func: function type
function func() {
}
func.__name__ = 'func';
func.__check__ = function(value) {
return is_func(value);
};
// array: array type
function array(t_item) {
if (!is_valid_type(t_item)) {
throw new Error('Invalid element type for array: ' + t_item);
}
var _array = function() {};
_array.__name__ = generate_type_name(array, t_item);
_array.__check__ = function(value) {
if (!is_array(value)) {
return false;
}
for (var i = 0; i < value.length; ++i) {
if (true != check(t_item, value[i])) {
return false;
}
}
return true;
};
return _array;
}
array.__name__ = 'array';
array.__check__ = array(any).__check__;
// tuple: tuple type
function tuple() {
var _args = arguments;
var _arg_names = [];
for (var i in _args) {
if (!is_valid_type(_args[i])) {
throw new Error('Invalid element type for tuple ' + _args[i]);
}
if (_args[i].__name__) {
_arg_names.push(_args[i].__name__);
}
else if (is_json(_args[i])) {
_arg_names.push('JSON');
}
}
var _tuple = function() {};
_tuple.__name__ = generate_type_name(tuple, _arg_names);
_tuple.__check__ = function(value) {
if (!is_array(value)) {
return false;
}
if (_args.length != value.length) {
return false;
}
for (var i = 0; i < _args.length; ++i) {
if (true != check(_args[i], value[i])) {
return false;
}
}
return true;
};
return _tuple;
}
// table: table type
function table() {
var _args = arguments;
var _table = function() {};
for (var i = 0; i < _args.length; ++i) {
}
_table.__name__ = generate_type_name(table, _args);
_table.__check__ = array(tuple.apply(null, _args)).__check__;
return _table;
}
//---- built-in types
//++++ module exports
var typing = function(type, value) {
return check(type, value);
};
typing.define = define;
typing.check= check;
typing.type = type;
typing.and = and;
typing.or = or;
typing.any = any;
typing.nullable = nullable;
typing.bool = bool;
typing.int = int;
typing.num = num;
typing.char = char;
typing.str = str;
typing.enumeration = enumeration;
typing.func = func;
typing.array = array;
typing.tuple = tuple;
typing.table = table;
module.exports = typing;
//---- module exports