'use strict';
const verbs = require('./ValidVerbs');
const operands = require('./operands');
const _ = require('lodash');
const dateTypeVerbs = [
'minutes',
'hours',
'days',
'weeks',
'months',
'quarters',
'years'];
let Handler = null;
/**
* @classdesc Commons Library singleton.<br>
* <p>
* This class provide some commons libraries <br>
* <b>Note 1:</b> for call the method use the namespace<br>
* </p>
* @class
* @hideconstructor
*
*/
class Commons {
/**
* @private
*/
constructor() {
/* eslint-disable no-extend-native*/
String.prototype.replaceAll = function(search, replacement) {
const target = this;
return target.split(search).
join(replacement);
};
Date.prototype.unix = function() {
return Number.parseInt(this.getTime() / 1000);
};
/* eslint-enable no-extend-native*/
Handler = this;
}
/**
* @desc <p>namespace:<b>commons</b></p>
* @example Commons.commons.replaceAll('test','t','') => `es`
* @param {string} target
* @param {string} search
* @param {string} replacement
* @return {string}
*/
replaceAll(target, search, replacement) {
return target.split(search).
join(replacement);
}
/**
* @desc <p>namespace:<b>validator</b></p><br><p> check for null, empty string, undefined and Object properties </p>
* @example Commons.validator.isEmpty({}) => true
* @param {Object} obj
* @param {boolean} checkEmptyObject if set true will check the object has any properties default is enable
* @return {boolean}
*/
isEmpty(obj, checkEmptyObject) {
if (checkEmptyObject === undefined) {
checkEmptyObject = true;
}
return (obj === null || obj === '' || obj === undefined
|| ( checkEmptyObject && !Object.keys(obj).length ));
}
/**
* @desc <p>namespace:<b>validator</b></p>
* @example Commons.validator.isLanguageISO('en-us') => true
* @param {string} lang - language code
* @todo put the ISO code number for docs
* @return {boolean} will be true if the language code match with ISO XXX
*/
isLanguageISO(lang) {
let output = true;
try {
const lcid = require('lcid');
const formattedLCID = Object.keys(lcid.all).
reduce((p, v) => {
p[v.toUpperCase().
replace('_', '-')] = lcid.all[v];
return p;
}, {});
if (lang.split(';').
reduce((p, v) => {
const tmp = v.split(',').
filter(vv => vv.length !== 0);
tmp.forEach(vvv => {
p.push(vvv.toUpperCase());
});
return p;
}, []).
filter(lang => formattedLCID[lang] !== undefined).length === 0) {
throw new Error();
}
}
catch (e) {
output = false;
}
return output;
}
/**
* @desc <p>namespace:<b>validator</b></p>
* @param {string} verb
* @return {boolean} will be true if the verb be in {@link verbList}
*/
isValidVerb(verb) {
return verbs[verb] !== undefined;
}
/**
* @desc <p>namespace:<b>validator</b></p>
* @param {string} key
* @return {boolean} will be true if the input be one of [user,bookings,location]
*/
topKeyValidator(key) {
let output = false;
switch (key.toLowerCase()) {
case 'device':
case 'booking':
case 'flight':
case 'location':
output = true;
break;
}
return output;
}
/**
* @desc <p>namespace:<b>parser</b></p><br><p><b>note:</b> the verbs in string should be in `{*VerbName*}`</p>
* @param {string} sentences -
* @return {*}
*/
getVerbsInString(sentences) {
// space splitter
const vars = sentences.split(' ').
reduce((p, v) => {
// , splitter
if (v.split(',').length !== 1) {
v.split(',').
forEach(parts => {
p.push(parts);
});
}
// ; splitter
else if (v.split(';').length !== 1) {
v.split(';').
forEach(parts => {
p.push(parts);
});
}
// splitter
else if (v.split('.').length !== 1) {
v.split('.').
forEach(parts => {
p.push(parts);
});
}
// ! splitter
else if (v.split('!').length !== 1) {
v.split('!').
forEach(parts => {
p.push(parts);
});
}
// ? splitter
else if (v.split('?').length !== 1) {
v.split('?').
forEach(parts => {
p.push(parts);
});
}
// : splitter
else if (v.split(':').length !== 1) {
v.split(':').
forEach(parts => {
p.push(parts);
});
}
else {
p.push(v);
}
return p;
}, []).
filter(parts => parts.match(/\{\*.*\*\}/g) !== null);
if (!vars.length) {
return [];
}
else {
const filteredVars = vars.filter(part => {
const tmp = part.replaceAll('*}', '').
replaceAll('{*', '').
toLowerCase();
return Handler.isValidVerb(tmp);
});
if (!filteredVars.length) {
throw new Error(' the var(s)' + JSON.stringify(vars) +
' is(are) not in the verb list');
}
else {
return filteredVars.map(parts => {
const tmp = Handler.replaceAll(Handler.replaceAll(parts, '*}', ''),
'{*', '').
toLowerCase();
return {name: parts, target: verbs[tmp]};
});
}
}
}
/**
* @desc <p>namespace:<b>commons</b><p>
* @param {string} verb name {@link verbList}
* @return {JSON} verb config
*/
getVerbConfig(verb) {
return verbs[verb] || null;
}
/**
* @desc <p>namespace:<b>validator</b><p>
* @param {JSON} value
* @param {JSON} acceptableOperands
* @return {boolean} true if the requested operands match all with acceptable operands under the fields config
*/
validRules(value, acceptableOperands) {
const requestedOperands = Object.keys(value);
return requestedOperands.filter(requestedOperand => acceptableOperands.indexOf(requestedOperand) !== -1).length ===
requestedOperands.length;
}
/**
* @desc <p>namespace:<b>validator</b><p>
* @param {JSON} values
* @return {boolean}
*/
paramValidator(values) {
let res = false;
Object.keys(values).
forEach(operandKey => {
let parts = false;
if (operands[operandKey].isDateTime) {
// / in date time comparing we have complex query
// todo implement load value structure from operands config file.
if (operandKey.toLowerCase() !== 'today' &&
operands[operandKey].type.val === undefined
&& values[operandKey].val === undefined) {
res = parts || true;
}
if (operandKey.toLowerCase() === 'today') {
res = true;
}
// to handel equal exact date
else if (operandKey === 'equalExactDate' &&
values[operandKey].specificDate) {
res = true;
}
else {
const value = values[operandKey];
res = parts || (typeof value.val === 'string' &&
value.val.length !== 0 &&
value.val.split(' ').length === 2 &&
dateTypeVerbs.indexOf(value.val.split(' ')[1].toLowerCase()) !==
-1);
}
}
else if (_.isObject(values[operandKey]) &&
Object.keys(values[operandKey]).length === 1
&& values[operandKey].target &&
typeof values[operandKey].target === 'string') {
// For compare two pair.
parts = this.isValidVerb(values[operandKey].target);
res = parts || res;
}
else {
operands[operandKey].type.split('|').
forEach(type => {
switch (type) {
case 'string':
parts = typeof values[operandKey] === 'string';
break;
case 'number':
parts = typeof values[operandKey] === 'number';
break;
case 'array':
parts = Array.isArray(values[operandKey]);
break;
}
res = parts || res;
});
}
});
return res;
}
/**
* @private
* @param {JSON}param
* @param {JSON}rule
* @param {function}exec
* @return {boolean}
*/
ruleValidator(param, rule, exec) {
const rulesKey = Object.keys(rule);
let res = true;
for (let i = 0, ruleKey = rulesKey[i]; i < rulesKey.length && res; i++) {
res = res && exec(param[ruleKey], rule[ruleKey]);
}
return res;
}
/**
*
* @param {JSON} params
* @param {JSON} rules
* @return {boolean}
*/
ruleGeneralValidator(params, rules) {
return Handler.ruleValidator(params, rules, (param, rule) => {
let res = null;
if (Array.isArray(rule)) {
// should check in list
res = rule.indexOf(param) !== -1;
}
else if (typeof rule === 'string' || typeof rule === 'number') {
res = param === rule;
}
else {
let andRes = true;
Object.keys(rule).
forEach(operands => {
switch (operands) {
case '<':
andRes = andRes && ( param < rule['<']);
break;
case '>':
andRes = andRes && ( param > rule['>']);
break;
case '!': // not in list
if (Array.isArray(rule['!'])) {
// not in list
andRes = andRes &&
!rule['!'].filter(val => val === param).length;
}
else {
// not eql
andRes = andRes && param !== rule['!'];
}
break;
case 'like':
try {
andRes = andRes && new RegExp('^'+rule['like'].replace(/%/g,'(.*)').
replace(/_/g,'(.)').
replace(/\[/g,'').
replace(/]/g,'')+'$').test(param);
}
catch (err){
andRes=false;
}
break;
}
});
res = res || andRes;
}
return res;
});
}
/**
*
* @param {json} params
* @param {json} rules
* @return {boolean}
*/
ruleDateValidator(params, rules) {
const moment = require('moment');
return Handler.ruleValidator(params, rules, (param, rule) => {
const compareTools = function(andRes, operands) {
const compareIt = function(a, b, operands) {
let functionName = {
'<=': 'isSameOrBefore',
'>=': 'isSameOrAfter',
'==': 'isSame',
};
if (rule.compareOptions.yy && rule.compareOptions.mm &&
rule.compareOptions.dd && rule.compareOptions.h &&
rule.compareOptions.m && rule.compareOptions.s) {
return a[functionName[operands]](b);
}
const compare = function(a, b, op) {
const fn = new Function('a', 'b', 'return a ' + op + 'b;');
return fn.call({}, a, b);
};
let out = true;
if (!rule.compareOptions.yy &&
(rule.compareOptions.mm || rule.compareOptions.dd)) {
if (rule.compareOptions.yy) {
out = out && compare(a.year(), b.year(), operands);
}
if (out && rule.compareOptions.mm) {
out = out && compare(a.month(), b.month(), operands);
}
if (out && rule.compareOptions.dd) {
out = out && compare(a.date(), b.date(), operands);
}
if (out && rule.compareOptions.h) {
out = out && compare(a.hour(), b.hour(), operands);
}
if (out && rule.compareOptions.m) {
out = out && compare(a.minutes(), b.minutes(), operands);
}
if (out && rule.compareOptions.s) {
out = out && compare(a.seconds(), b.seconds(), operands);
}
}
else {
functionName = functionName[operands];
if (rule.compareOptions.yy) {
out = out && a[functionName](b, 'years');
}
if (out && rule.compareOptions.mm) {
out = out && a[functionName](b, 'months');
}
if (out && rule.compareOptions.dd) {
out = out && a[functionName](b, 'days');
}
if (out && rule.compareOptions.h) {
out = out && a[functionName](b, 'hours');
}
if (out && rule.compareOptions.m) {
out = out && a[functionName](b, 'minutes');
}
if (out && rule.compareOptions.s) {
out = out && a[functionName](b, 'seconds');
}
}
return out;
};
if (param === null || param === undefined) {
return false;
}
return andRes &&
compareIt(moment(param).
utc(), rule[operands].utc(), operands);
};
return Object.keys(rule).
filter(key => key !== 'compareOptions').
reduce(compareTools, true);
});
}
/**
*
* @param {json} params
* @param {json} rules
* @return {boolean}
*/
ruleInDeepValidator(params, rules) {
return true;
}
/**
* @desc check the passed String param contain a date value.
* @param {String}target
* @return {boolean}
*/
isValidDate(target) {
const d = new Date(target);
if (Object.prototype.toString.call(d) === '[object Date]') {
// it is a date
if (isNaN(d.getTime())) { // d.valueOf() could also work
// date is not valid
return false;
}
else {
// date is valid
return true;
}
}
else {
// not a date
return false;
}
}
/**
*
* @param {{fieldName:string,config:{fieldName:string}}} rules
* @return {boolean}
*/
filedNameMachs(rules) {
return rules.fieldName === rules.config.fieldName;
}
/**
* @desc Returns an array with arrays of the given size.
* @param {Array} myArray Array to split
* @param {Number} chunkSize Size of every group
* @return {Array}
*/
chunkArray(myArray, chunkSize) {
const results = [];
while (myArray.length) {
results.push(myArray.splice(0, chunkSize));
}
return results;
}
/**
* @desc Rune the logical operand on the val1 and val2 for deep linking
* @param {T|Array} val1 The first value.
* @param {T|Array} val2 The second value.
* @param {String} op The operand for compare.
* @param {Object} mapper the map guide for map first val to second val;
* @return {Boolean}
*/
logicalConfirmDeepMap(val1, val2, op, mapper) {
if (!val1 || !val2 ) {
// If any of val is null return false.
return false;
}
let res=false;
const mappedVal2=mapper?Array.isArray(val2)? val2.map(key=>mapper[key]): mapper[val2]:null;
switch (op) {
case 'eql':
if (mapper && Array.isArray(val1) && Array.isArray(mappedVal2)) {
res = val1.some(key=>mappedVal2.indexOf(key)!==-1);
}
else if (mapper && Array.isArray(val1) && !Array.isArray(mappedVal2)) {
res = val1.some(key=>key===mappedVal2);
}
else if (mapper && !Array.isArray(val1) && Array.isArray(mappedVal2)) {
res = mappedVal2.some(key=> key===val1);
}
else if (!mapper) {
res= val1===val2;
}
else {
res = mappedVal2 === val1;
}
break;
}
return res;
}
}
module.exports = (function() {
let instance = null;
/**
* @desc make Commons lib singleton.
* @return {Commons}
*/
function createInstance() {
return new Commons();
}
return {
getInstance: function() {
if (!instance) {
const tmp = createInstance();
instance = {
commons: {
replaceAll: tmp.replaceAll,
getVerbConfig: tmp.getVerbConfig,
chunkArray: tmp.chunkArray,
},
validator: {
isEmpty: tmp.isEmpty,
isLanguageISO: tmp.isLanguageISO,
isValidVerb: tmp.isValidVerb,
topKeyValidator: tmp.topKeyValidator,
validRules: tmp.validRules,
paramValidator: tmp.paramValidator,
ruleGeneralValidator: tmp.ruleGeneralValidator,
ruleDateValidator: tmp.ruleDateValidator,
ruleInDeepValidator: tmp.ruleInDeepValidator,
filedNameMachs: tmp.filedNameMachs,
isValidDate: tmp.isValidDate,
logicalConfirmDeepMap: tmp.logicalConfirmDeepMap,
},
parser: {
getVerbsInString: tmp.getVerbsInString,
},
};
}
return instance;
},
};
})();