@yolain/validation
Version:
Validation library inspired by Laravel's Validator
736 lines (606 loc) • 15.5 kB
JavaScript
var isDateValid = require('date-fns/isValid');
var parseISO = require('date-fns/parseISO');
function leapYear(year) {
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
}
function isValidDate(inDate) {
if (inDate instanceof Date) {
return !isNaN(inDate);
}
// reformat if supplied as mm.dd.yyyy (period delimiter)
if (typeof inDate === 'string') {
var pos = inDate.indexOf('.');
if (pos > 0 && pos <= 6) {
inDate = inDate.replace(/\./g, '-');
}
if (inDate.length === 10) {
return isDateValid(parseISO(inDate));
}
}
var testDate = new Date(inDate);
var yr = testDate.getFullYear();
var mo = testDate.getMonth();
var day = testDate.getDate();
var daysInMonth = [31, leapYear(yr) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
if (yr < 1000) {
return false;
}
if (isNaN(mo)) {
return false;
}
if (mo + 1 > 12) {
return false;
}
if (isNaN(day)) {
return false;
}
if (day > daysInMonth[mo]) {
return false;
}
return true;
}
function isJson(str){
if (typeof str == 'string') {
try {
let obj=JSON.parse(str);
if(typeof obj == 'object' && obj ){
return true;
}else{
return false;
}
} catch(e) {
//console.log('json:'+str+'!!!'+e);
return false;
}
}
}
var rules = {
required: function(val) {
var str;
if (val === undefined || val === null) {
return false;
}
str = String(val).replace(/\s/g, '');
return str.length > 0 ? true : false;
},
required_if: function(val, req, attribute) {
req = this.getParameters();
if (this.validator._objectPath(this.validator.input, req[0]) === req[1]) {
return this.validator.getRule('required').validate(val);
}
return true;
},
required_unless: function(val, req, attribute) {
req = this.getParameters();
if (this.validator._objectPath(this.validator.input, req[0]) !== req[1]) {
return this.validator.getRule('required').validate(val);
}
return true;
},
required_with: function(val, req, attribute) {
if (this.validator._objectPath(this.validator.input, req)) {
return this.validator.getRule('required').validate(val);
}
return true;
},
required_with_all: function(val, req, attribute) {
req = this.getParameters();
for (var i = 0; i < req.length; i++) {
if (!this.validator._objectPath(this.validator.input, req[i])) {
return true;
}
}
return this.validator.getRule('required').validate(val);
},
required_without: function(val, req, attribute) {
if (this.validator._objectPath(this.validator.input, req)) {
return true;
}
return this.validator.getRule('required').validate(val);
},
required_without_all: function(val, req, attribute) {
req = this.getParameters();
for (var i = 0; i < req.length; i++) {
if (this.validator._objectPath(this.validator.input, req[i])) {
return true;
}
}
return this.validator.getRule('required').validate(val);
},
boolean: function(val) {
return (
val === true ||
val === false ||
val === 0 ||
val === 1 ||
val === '0' ||
val === '1' ||
val === 'true' ||
val === 'false'
);
},
// compares the size of strings
// with numbers, compares the value
size: function(val, req, attribute) {
if (val) {
req = parseFloat(req);
var size = this.getSize();
return size === req;
}
return true;
},
string: function(val, req, attribute) {
return typeof val === 'string';
},
sometimes: function(val) {
return true;
},
/**
* Compares the size of strings or the value of numbers if there is a truthy value
*/
min: function(val, req, attribute) {
var size = this.getSize();
return size >= req;
},
/**
* Compares the size of strings or the value of numbers if there is a truthy value
*/
max: function(val, req, attribute) {
var size = this.getSize();
return size <= req;
},
between: function(val, req, attribute) {
req = this.getParameters();
var size = this.getSize();
var min = parseFloat(req[0], 10);
var max = parseFloat(req[1], 10);
return size >= min && size <= max;
},
email: function(val) {
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(val);
},
numeric: function(val) {
var num;
num = Number(val); // tries to convert value to a number. useful if value is coming from form element
if (typeof num === 'number' && !isNaN(num) && typeof val !== 'boolean') {
return true;
} else {
return false;
}
},
array: function(val) {
return val instanceof Array;
},
url: function(url) {
return /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_\+.~#?&/=]*)/i.test(url);
},
alpha: function(val) {
return /^[a-zA-Z]+$/.test(val);
},
alpha_dash: function(val) {
return /^[a-zA-Z0-9_\-]+$/.test(val);
},
alpha_num: function(val) {
return /^[a-zA-Z0-9]+$/.test(val);
},
same: function(val, req) {
var val1 = this.validator._flattenObject(this.validator.input)[req];
var val2 = val;
if (val1 === val2) {
return true;
}
return false;
},
different: function(val, req) {
var val1 = this.validator._flattenObject(this.validator.input)[req];
var val2 = val;
if (val1 !== val2) {
return true;
}
return false;
},
in: function(val, req) {
var list, i;
if (val) {
list = this.getParameters();
}
if (val && !(val instanceof Array)) {
var localValue = val;
for (i = 0; i < list.length; i++) {
if (typeof list[i] === 'string') {
localValue = String(val);
}
if (localValue === list[i]) {
return true;
}
}
return false;
}
if (val && val instanceof Array) {
for (i = 0; i < val.length; i++) {
if (list.indexOf(val[i]) < 0) {
return false;
}
}
}
return true;
},
not_in: function(val, req) {
var list = this.getParameters();
var len = list.length;
var returnVal = true;
for (var i = 0; i < len; i++) {
var localValue = val;
if (typeof list[i] === 'string') {
localValue = String(val);
}
if (localValue === list[i]) {
returnVal = false;
break;
}
}
return returnVal;
},
accepted: function(val) {
if (val === 'on' || val === 'yes' || val === 1 || val === '1' || val === true) {
return true;
}
return false;
},
confirmed: function(val, req, key) {
var confirmedKey = key + '_confirmation';
if (this.validator.input[confirmedKey] === val) {
return true;
}
return false;
},
integer: function(val) {
return String(parseInt(val, 10)) === String(val);
},
digits: function(val, req) {
var numericRule = this.validator.getRule('numeric');
if (numericRule.validate(val) && String(val).length === parseInt(req)) {
return true;
}
return false;
},
digits_between: function(val) {
var numericRule = this.validator.getRule('numeric');
var req = this.getParameters();
var valueDigitsCount = String(val).length;
var min = parseFloat(req[0], 10);
var max = parseFloat(req[1], 10);
if (numericRule.validate(val) && valueDigitsCount >= min && valueDigitsCount <= max) {
return true;
}
return false;
},
regex: function(val, req) {
var mod = /[g|i|m]{1,3}$/;
var flag = req.match(mod);
flag = flag ? flag[0] : '';
req = req.replace(mod, '').slice(1, -1);
req = new RegExp(req, flag);
return !!req.test(val);
},
date: function(val, format) {
return isValidDate(val);
},
present: function(val) {
return typeof val !== 'undefined';
},
after: function(val, req) {
var val1 = this.validator.input[req];
var val2 = val;
if (!isValidDate(val1)) {
return false;
}
if (!isValidDate(val2)) {
return false;
}
if (new Date(val1).getTime() < new Date(val2).getTime()) {
return true;
}
return false;
},
after_or_equal: function(val, req) {
var val1 = this.validator.input[req];
var val2 = val;
if (!isValidDate(val1)) {
return false;
}
if (!isValidDate(val2)) {
return false;
}
if (new Date(val1).getTime() <= new Date(val2).getTime()) {
return true;
}
return false;
},
before: function(val, req) {
var val1 = this.validator.input[req];
var val2 = val;
if (!isValidDate(val1)) {
return false;
}
if (!isValidDate(val2)) {
return false;
}
if (new Date(val1).getTime() > new Date(val2).getTime()) {
return true;
}
return false;
},
before_or_equal: function(val, req) {
var val1 = this.validator.input[req];
var val2 = val;
if (!isValidDate(val1)) {
return false;
}
if (!isValidDate(val2)) {
return false;
}
if (new Date(val1).getTime() >= new Date(val2).getTime()) {
return true;
}
return false;
},
hex: function(val) {
return /^[0-9a-f]+$/i.test(val);
},
json:function (val){
return isJson(val);
}
};
var missedRuleValidator = function() {
throw new Error('Validator `' + this.name + '` is not defined!');
};
var missedRuleMessage;
function Rule(name, fn, async) {
this.name = name;
this.fn = fn;
this.passes = null;
this._customMessage = undefined;
this.async = async;
}
Rule.prototype = {
/**
* Validate rule
*
* @param {mixed} inputValue
* @param {mixed} ruleValue
* @param {string} attribute
* @param {function} callback
* @return {boolean|undefined}
*/
validate: function(inputValue, ruleValue, attribute, callback) {
var _this = this;
this._setValidatingData(attribute, inputValue, ruleValue);
if (typeof callback === 'function') {
this.callback = callback;
var handleResponse = function(passes, message) {
_this.response(passes, message);
};
if (this.async) {
return this._apply(inputValue, ruleValue, attribute, handleResponse);
} else {
return handleResponse(this._apply(inputValue, ruleValue, attribute));
}
}
return this._apply(inputValue, ruleValue, attribute);
},
/**
* Apply validation function
*
* @param {mixed} inputValue
* @param {mixed} ruleValue
* @param {string} attribute
* @param {function} callback
* @return {boolean|undefined}
*/
_apply: function(inputValue, ruleValue, attribute, callback) {
var fn = this.isMissed() ? missedRuleValidator : this.fn;
return fn.apply(this, [inputValue, ruleValue, attribute, callback]);
},
/**
* Set validating data
*
* @param {string} attribute
* @param {mixed} inputValue
* @param {mixed} ruleValue
* @return {void}
*/
_setValidatingData: function(attribute, inputValue, ruleValue) {
this.attribute = attribute;
this.inputValue = inputValue;
this.ruleValue = ruleValue;
},
/**
* Get parameters
*
* @return {array}
*/
getParameters: function() {
var value = [];
if (typeof this.ruleValue === 'string') {
value = this.ruleValue.split(',');
}
if (typeof this.ruleValue === 'number') {
value.push(this.ruleValue);
}
if (this.ruleValue instanceof Array) {
value = this.ruleValue;
}
return value;
},
/**
* Get true size of value
*
* @return {integer|float}
*/
getSize: function() {
var value = this.inputValue;
if (value instanceof Array) {
return value.length;
}
if (typeof value === 'number') {
return value;
}
if (this.validator._hasNumericRule(this.attribute)) {
return parseFloat(value, 10);
}
return value.length;
},
/**
* Get the type of value being checked; numeric or string.
*
* @return {string}
*/
_getValueType: function() {
if (typeof this.inputValue === 'number' || this.validator._hasNumericRule(this.attribute)) {
return 'numeric';
}
return 'string';
},
/**
* Set the async callback response
*
* @param {boolean|undefined} passes Whether validation passed
* @param {string|undefined} message Custom error message
* @return {void}
*/
response: function(passes, message) {
this.passes = passes === undefined || passes === true;
this._customMessage = message;
this.callback(this.passes, message);
},
/**
* Set validator instance
*
* @param {Validator} validator
* @return {void}
*/
setValidator: function(validator) {
this.validator = validator;
},
/**
* Check if rule is missed
*
* @return {boolean}
*/
isMissed: function() {
return typeof this.fn !== 'function';
},
get customMessage() {
return this.isMissed() ? missedRuleMessage : this._customMessage;
}
};
var manager = {
/**
* List of async rule names
*
* @type {Array}
*/
asyncRules: [],
/**
* Implicit rules (rules to always validate)
*
* @type {Array}
*/
implicitRules: [
'required',
'required_if',
'required_unless',
'required_with',
'required_with_all',
'required_without',
'required_without_all',
'accepted',
'present'
],
/**
* Get rule by name
*
* @param {string} name
* @param {Validator}
* @return {Rule}
*/
make: function(name, validator) {
var async = this.isAsync(name);
var rule = new Rule(name, rules[name], async);
rule.setValidator(validator);
return rule;
},
/**
* Determine if given rule is async
*
* @param {string} name
* @return {boolean}
*/
isAsync: function(name) {
for (var i = 0, len = this.asyncRules.length; i < len; i++) {
if (this.asyncRules[i] === name) {
return true;
}
}
return false;
},
/**
* Determine if rule is implicit (should always validate)
*
* @param {string} name
* @return {boolean}
*/
isImplicit: function(name) {
return this.implicitRules.indexOf(name) > -1;
},
/**
* Register new rule
*
* @param {string} name
* @param {function} fn
* @return {void}
*/
register: function(name, fn) {
rules[name] = fn;
},
/**
* Register new implicit rule
*
* @param {string} name
* @param {function} fn
* @return {void}
*/
registerImplicit: function(name, fn) {
this.register(name, fn);
this.implicitRules.push(name);
},
/**
* Register async rule
*
* @param {string} name
* @param {function} fn
* @return {void}
*/
registerAsync: function(name, fn) {
this.register(name, fn);
this.asyncRules.push(name);
},
/**
* Register implicit async rule
*
* @param {string} name
* @param {function} fn
* @return {void}
*/
registerAsyncImplicit: function(name, fn) {
this.registerImplicit(name, fn);
this.asyncRules.push(name);
},
registerMissedRuleValidator: function(fn, message) {
missedRuleValidator = fn;
missedRuleMessage = message;
}
};
module.exports = manager;