node-fit-model
Version:
Checks whether an object is fits into a model.
307 lines (257 loc) • 8.35 kB
JavaScript
;
/**
* The model starts here
* @param modelDir
* @constructor
*/
// Read js files from modelDir
var fs = require('fs'),
fileMap;// private
function Model(obj)
{
if(!obj) throw new Error("Paramters must be set");
var modelDir = obj.modelDir,
postFix = obj.postFix,
shouldThrow = obj.shouldThrow;
if(!modelDir) throw new Error("modelDir must be defined in options");
if(!postFix) console.log('Warning : Not defining postFix will make you type more');
if(shouldThrow === undefined) shouldThrow = true;
// Filemap to just read now and cache
fileMap = {};
// Map all files data into cache
var files = fs.readdirSync(modelDir);
files.forEach(function(file){
var fileName = mapFileName(file, postFix);
if(fileMap[fileName]) return;
var data = require(modelDir + '/' + file);
if(typeof data === 'string') data = JSON.parse(data);
if(!isArray(data)) throw new Error("Models in file : " + file + " needs to be an array");
fileMap[fileName] = data;
});
};
/**
* Map file name so it can be cached
* @param fileName
* @param postFix
* @returns {string}
*/
var mapFileName = function(fileName, postFix){
var postRegExp = new RegExp(postFix + '$', '');
return fileName.replace(postRegExp, '').toLowerCase();
};
/**
* Is Array or not
* @param someVar
* @returns {boolean}
*/
var isArray = function(someVar) {
return Object.prototype.toString.call( someVar ) === '[object Array]';
};
/**
*
* @param modelName
* @param data
*/
Model.prototype.fitModel = function(modelName, data) {
return checkModel(modelName, data);
};
/**
* Check Model
* @param modelName
* @param data
* @param iteration Used so that cached data is used
*/
var checkModel = function(modelName, data)
{
if(!modelName) throw new Error("modelName needs to be defined");
var model = fileMap[modelName.toLowerCase()];
if(!model) throw new Error("Invalid model name provided => " + modelName);
model.forEach(function(row) {
var param = row.name,
value = data[param],
required = row.required,
canBeNull = row.canBeNull,
dataType = row.type,
options = row.options,
maxLength = row.maxLength,
minLength = row.minLength,
canBeEmpty = row.canBeEmpty;
if(!validateRequired(value, required, canBeNull))
{
throw new Error("Value of " + row.name + " is required. Actual => " + value);
}
// If you're here and value is undefined,
// it is not required to be there
// so don't worry about other features
if(typeof value === 'undefined') return;
if(!validateDataType(data, param, value, dataType, options, canBeNull, canBeEmpty))
{
if(required) {
if (dataType == 'enum') {
throw new Error("Value of " + row.name + " must be enum and have a value in [ " + options.join(', ') + " ]. Actual => " + value);
}
else if(canBeEmpty) {
if((typeof value === 'string' && value.length)
|| (Object.keys(value).length))
{
throw new Error("Value of " + row.name + " must be " + dataType + ". Actual => " + value);
}
}
else {
throw new Error("Value of " + row.name + " must be " + dataType + ". Actual => " + value);
}
}
}
if(!validateMaxLength(value, maxLength))
{
throw new Error("Value of " + value + " must have a length of at most " + maxLength);
}
if(!validateMinLength(value, minLength))
{
throw new Error("Value of " + value + " must have a length of at least " + minLength);
}
});
return true;
}
/**
* Is it required
* @param value
* @param required
* @returns {*|boolean}
*/
var validateRequired = function(value, required, canBeNull) {
return !required || // Not required
(typeof value !== 'undefined' && value !== null) || // Value just exists. It's ok
(canBeNull && value === null); // it's defined somehow and it can be null
};
/**
* Validate the data with model's data type
* @param value
* @param dataType
* @param options
*/
var validateDataType = function(data, param, value, dataType, options, canBeNull, canBeEmpty)
{
if(!value && canBeNull) return true;
var verified = false;
switch(dataType) {
case "int":
case "integer":
verified = isInt(value);
break;
case "timestamp":
// Check if it can be parsed as a Date object
var d = new Date(value);
verified = !isNaN(d.getTime());
break;
case "float":
case "double":
verified = isFloat(value) || isInt(value); // value can be 0.0 and be parsed as 0
break;
case "number": // int or float
verified = isNumber(value);
break;
case "string":
verified = typeof value === 'string';
break;
case "enum":
// Options must be set in enum
verified = options && options.indexOf(value) > -1;
break;
case "bool":
verified = typeof value === 'boolean';
break;
default:
// Object type
if(/object/i.test(dataType))
{
// Object or an Array
var matches = dataType.match(/\(.*\)/);
if(!matches || !matches.length) verified = typeof value === 'object' && Object.prototype.toString.call( value ) !== '[object Object]';
else {
var model = matches[0].replace(/\(|\)/g, '');
if (!model) throw new Error("Model in the object is undefined. Probably a syntax error.");
verified = canBeEmpty || checkModel(model, data[param]);
}
}
// Array type
else if(/array/i.test(dataType))
{
var matches = dataType.match(/\(.*\)/);
if(!matches || !matches.length) verified = Object.prototype.toString.call( value ) === '[object Array]';
else {
var model = matches[0].replace(/\(|\)/g, '');
if (!model) throw new Error("Model in the array is undefined. Probably a syntax error.");
// loop through all objects
for (var key in data[param]) {
verified = checkModel(model, data[param][key]);
if (!verified) break;
}
}
}
else throw new Error("Invalid data type: " + dataType + " is not in modelling data type list validateDataType");
}
return verified;
};
/**
* Best performance for finding isInt
* according to http://jsperf.com/tfm-is-integer
*
* @param value
* @returns {boolean}
*/
var isInt = function(value)
{
var x;
if (isNaN(value)) {
return false;
}
x = parseFloat(value);
return (x | 0) === x;
};
/**
* is Float
* @param n
* @returns {boolean}
*/
var isFloat = function(n)
{
n = parseFloat(n);
return n === +n && n !== (n|0);
};
/**
* IsNumber
* @param n
* @returns {boolean}
*/
var isNumber = function(n) {
return /^-?[\d.]+(?:e-?\d+)?$/.test(n);
};
/**
*
* @param value
* @param length
* @throws
* @returns {boolean}
*/
var validateMaxLength = function(value, length)
{
if(!length || typeof value === 'undefined') return true;
if(typeof value !== 'string') value = value.toString();
if(value.length > length) throw new Error("Length of " + value + " must be less than " + length);
return true;
};
/**
*
* @param value
* @param length
* @returns {boolean}
*/
var validateMinLength = function(value, length)
{
if(!length || typeof value === 'undefined') return true;
if(typeof value !== 'string') value = value.toString();
if(value.length < length) throw new Error("Length of " + value + " must be greater than " + length);
return true;
};
module.exports = Model;