UNPKG

simple-body-validator

Version:

This package is inspired by Laravel validation, and aims to make body validation easier for Javascript developers

723 lines (722 loc) 26.9 kB
'use strict'; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const date_1 = require("../utils/date"); const object_1 = require("../utils/object"); const general_1 = require("../utils/general"); const validationRuleParser_1 = __importDefault(require("./validationRuleParser")); class validateAttributes { constructor(data = {}, rules = {}) { this.data = data; this.rules = rules; } ; /** * Validate that an attribute was "accepted". * * This validation rule implies the attribute is "required". */ validateAccepted(value) { const acceptable = ['yes', 'on', '1', 1, true, 'true']; return this.validateRequired(value) && acceptable.indexOf(value) !== -1; } /** * Validate that an attribute was "accepted" when another attribute has a given value. */ validateAcceptedIf(value, parameters) { this.requireParameterCount(2, parameters, 'accepted_if'); const other = (0, object_1.deepFind)(this.data, parameters[0]); if (!other) { return true; } const values = parameters.slice(1); if (values.indexOf(other) !== -1) { return this.validateAccepted(value); } return true; } /** * Validate the date is after a given date. */ validateAfter(value, parameters) { this.requireParameterCount(1, parameters, 'after'); return this.compareDates(value, parameters[0], '>', 'after'); } ; /** * Validate the date is after or equal a given date. */ validateAfterOrEqual(value, parameters) { this.requireParameterCount(1, parameters, 'after_or_equal'); return this.compareDates(value, parameters[0], '>=', 'after_or_equal'); } ; /** * Validate that an attribute contains only alphabetic characters. */ validateAlpha(value) { const regex = /^[a-zA-Z]+$/; return typeof value === 'string' && regex.test(value); } ; /** * Validate that an attribute contains only alpha-numeric characters, dashes, and underscores. */ validateAlphaDash(value) { if (typeof value != 'string' && typeof value != 'number') { return false; } const regex = /^(?=.*[a-zA-Z0-9])[a-zA-Z0-9-_]+$/; return regex.test(value.toString()); } ; /** * Validate that an attribute contains only alpha-numeric characters. */ validateAlphaNum(value) { if (typeof value != 'string' && typeof value != 'number') { return false; } const regex = /^[a-zA-Z0-9]+$/; return regex.test(value.toString()); } /** * Validate that an attribute is an array */ validateArray(value) { return Array.isArray(value); } ; /** * Validate that an attribute is an array and that his values are unique */ validateArrayUnique(value) { if (!Array.isArray(value)) { return false; } return new Set(value).size === value.length; } ; /** * Always returns true - this method will be used in conbination with other rules and will be used to stop validation of first failure */ validateBail() { return true; } ; /** * Validate the date is before a given date. */ validateBefore(value, parameters) { this.requireParameterCount(1, parameters, 'before'); return this.compareDates(value, parameters[0], '<', 'before'); } /** * Validate the date is before or equal a given date. */ validateBeforeOrEqual(value, parameters) { this.requireParameterCount(1, parameters, 'before_or_equal'); return this.compareDates(value, parameters[0], '<=', 'before_or_equal'); } /** * Validate the size of an attribute is between a set of values */ validateBetween(value, parameters, attribute) { if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'object') { throw 'Validation rule between requires the field under validation to be a number, string, array, or object.'; } this.requireParameterCount(2, parameters, 'between'); let [min, max] = parameters; if (isNaN(min) || isNaN(max)) { throw 'Validation rule between requires both parameters to be numbers.'; } min = Number(min); max = Number(max); if (min >= max) { throw 'Validation rule between requires that the first parameter be greater than the second one.'; } const size = (0, general_1.getSize)(value, validationRuleParser_1.default.hasRule(attribute, (0, general_1.getNumericRules)(), this.rules)); return size >= min && size <= max; } ; /** * Validate that an attribute is boolean */ validateBoolean(value, parameters, attribute) { if (validationRuleParser_1.default.hasRule(attribute, 'strict', this.rules)) { return typeof value === 'boolean'; } const acceptable = [true, false, 0, 1, '0', '1']; return acceptable.indexOf(value) !== -1; } ; /** * Validate that an attribute has matching confirmation. */ validateConfirmed(value, parameters, attribute) { return this.validateSame(value, [`${attribute}_confirmation`]) || this.validateSame(value, [`${attribute}Confirmation`]); } ; /** * Validate that an attribute is a valid date. */ validateDate(value) { return (0, date_1.toDate)(value) ? true : false; } ; /** * Validate that an attribute is equal to another date. */ validateDateEquals(value, paramters) { this.requireParameterCount(1, paramters, 'date_equals'); return this.compareDates(value, paramters[0], '=', 'date_equals'); } ; /** * Validate that an attribute was "declined". * * This validation rule implies the attribute is "required". */ validateDeclined(value) { const acceptable = ['no', 'off', '0', 0, false, 'false']; return this.validateRequired(value) && acceptable.indexOf(value) !== -1; } ; /** * Validate that an attribute was "declined" when another attribute has a given value. */ validateDeclinedIf(value, parameters) { this.requireParameterCount(2, parameters, 'declined_if'); const other = (0, object_1.deepFind)(this.data, parameters[0]); if (!other) { return true; } const values = parameters.slice(1); if (values.indexOf(other) !== -1) { return this.validateDeclined(value); } return true; } ; /** * Validate that an attribute is different from another attribute. */ validateDifferent(value, parameters) { this.requireParameterCount(1, parameters, 'different'); const other = (0, object_1.deepFind)(this.data, parameters[0]); if (!(0, general_1.sameType)(value, other)) { return true; } if (value !== null && typeof value === 'object') { return !(0, object_1.deepEqual)(value, other); } return value !== other; } ; /** * Validate that an attribute has a given number of digits. */ validateDigits(value, parameters) { this.requireParameterCount(1, parameters, 'digits'); if ((0, general_1.isInteger)(parameters[0]) === false) { throw 'Validation rule digits requires the parameter to be an integer.'; } if (parameters[0] <= 0) { throw 'Validation rule digits requires the parameter to be an integer greater than 0.'; } if (typeof value !== 'string' && typeof value !== 'number') { return false; } value = value.toString(); return /^\d+$/.test(value) && value.length === parseInt(parameters[0]); } ; /** * Validate that an attribute is between a given number of digits. */ validateDigitsBetween(value, parameters) { this.requireParameterCount(2, parameters, 'digits_between'); let [min, max] = parameters; if ((0, general_1.isInteger)(min) === false || (0, general_1.isInteger)(max) === false) { throw 'Validation rule digits_between requires both parameters to be integers.'; } min = parseInt(min); max = parseInt(max); if (min <= 0 || max <= 0) { throw 'Validation rule digits_between requires the parameters to be an integer greater than 0.'; } if (min >= max) { throw 'Validation rule digits_between requires the max param to be greater than the min param.'; } if (typeof value !== 'string' && typeof value !== 'number') { return false; } value = value.toString(); const valueLength = value.length; return /^\d+$/.test(value) && valueLength >= min && valueLength <= max; } ; /** * Validate that an attribute is a valid email address. */ validateEmail(value) { if (typeof value !== 'string') { return false; } /** * Max allowed length for a top-level-domain is 24 characters. * reference to list of top-level-domains: https://data.iana.org/TLD/tlds-alpha-by-domain.txt */ return value.toLowerCase().match(/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,24})+$/) !== null; } ; /** * Validate the attribute ends with a given substring. */ validateEndsWith(value, parameters) { this.requireParameterCount(1, parameters, 'ends_with'); if (typeof value !== 'string') { throw 'The field under validation must be a string'; } const valueLength = value.length; for (let i = 0; i < parameters.length; i++) { if (typeof parameters[i] === 'string' && value.indexOf(parameters[i], valueLength - parameters[i].length) !== -1) { return true; } } return false; } ; /** * Validate that two attributes match. */ validateSame(value, paramaters) { this.requireParameterCount(1, paramaters, 'same'); const other = (0, object_1.deepFind)(this.data, paramaters[0]); if (!(0, general_1.sameType)(value, other)) { return false; } if (value !== null && typeof value === 'object') { return (0, object_1.deepEqual)(value, other); } return value === other; } ; /** * Validate the size of an attribute. */ validateSize(value, parameters, attribute) { this.requireParameterCount(1, parameters, 'size'); return (0, general_1.getSize)(value, validationRuleParser_1.default.hasRule(attribute, (0, general_1.getNumericRules)(), this.rules)) === Number(parameters[0]); } ; /** * Validate Optinial attributes. Always return true, just lets us put sometimes in rule. */ validateSometimes() { return true; } ; /** * Validate the attribute starts with a given substring. */ validateStartsWith(value, parameters) { this.requireParameterCount(1, parameters, 'starts_with'); if (typeof value !== 'string') { throw 'The field under validation must be a string'; } for (let i = 0; i < parameters.length; i++) { if (typeof parameters[i] === 'string' && value.substr(0, parameters[i].length) === parameters[i]) { return true; } } return false; } ; /** * Validate that a required attribute exists */ validateRequired(value) { if (value === null || typeof value === 'undefined') { return false; } else if (typeof value === 'string' && value.trim() === '') { return false; } else if (Array.isArray(value) && value.length < 1) { return false; } else if (typeof value === 'object' && Object.keys(value).length < 1) { return false; } return true; } ; /** * Validate that an attribute exists when another atteribute has a given value */ validateRequiredIf(value, parameters) { this.requireParameterCount(2, parameters, 'required_if'); const other = (0, object_1.deepFind)(this.data, parameters[0]); if (typeof other === 'undefined') { return true; } const values = this.parseDependentRuleParameters(other, parameters); if (values.indexOf(other) !== -1) { return this.validateRequired(value); } return true; } ; /** * Validate that an attribute exists when another attribute does not have a given value. */ validateRequiredUnless(value, parameters) { this.requireParameterCount(2, parameters, 'required_unless'); let other = (0, object_1.deepFind)(this.data, parameters[0]); other = typeof other === 'undefined' ? null : other; const values = this.parseDependentRuleParameters(other, parameters); if (values.indexOf(other) === -1) { return this.validateRequired(value); } return true; } ; /** * Validate that an attribute exists when any other attribute exists. */ validateRequiredWith(value, parameters) { if (!this.allFailingRequired(parameters)) { return this.validateRequired(value); } return true; } ; /** * Validate that an attribute exists when all other attributes exist. */ validateRequiredWithAll(value, parameters) { if (!this.anyFailingRequired(parameters)) { return this.validateRequired(value); } return true; } ; /** * Validate that an attribute exists when another attribute does not. */ validateRequiredWithout(value, parameters) { if (this.anyFailingRequired(parameters)) { return this.validateRequired(value); } return true; } ; /** * Validate that an attribute exists when all other attributes do not. */ validateRequiredWithoutAll(value, parameters) { if (this.allFailingRequired(parameters)) { return this.validateRequired(value); } return true; } ; /** * Determine if any of the given attributes fail the required test. */ anyFailingRequired(attributes) { for (let i = 0; i < attributes.length; i++) { if (!this.validateRequired((0, object_1.deepFind)(this.data, attributes[i]))) { return true; } } return false; } ; /** * Determine if all of the given attributes fail the required test. */ allFailingRequired(attributes) { for (let i = 0; i < attributes.length; i++) { if (this.validateRequired((0, object_1.deepFind)(this.data, attributes[i]))) { return false; } } return true; } ; /** * Validate that an attribute is a string. */ validateString(value) { return typeof value === 'string'; } ; /** * Validate the size of an attribute is less than a maximum value. */ validateMax(value, parameters, attribute) { this.requireParameterCount(1, parameters, 'max'); if (isNaN(parameters[0])) { throw 'Validation rule max requires parameter to be a number.'; } const size = (0, general_1.getSize)(value, validationRuleParser_1.default.hasRule(attribute, (0, general_1.getNumericRules)(), this.rules)); return size <= Number(parameters[0]); } ; /** * Validate the size of an attribute is greater than a minimum value. */ validateMin(value, parameters, attribute) { this.requireParameterCount(1, parameters, 'min'); if (isNaN(parameters[0])) { throw 'Validation rule min requires parameter to be a number.'; } const size = (0, general_1.getSize)(value, validationRuleParser_1.default.hasRule(attribute, (0, general_1.getNumericRules)(), this.rules)); return size >= Number(parameters[0]); } ; /** * Validate that an attribute is numeric. */ validateNumeric(value, parameters, attribute) { if (validationRuleParser_1.default.hasRule(attribute, 'strict', this.rules) && typeof value !== 'number') { return false; } return value !== null && isNaN(value) === false; } ; /** * Validate that an attribute is an object */ validateObject(value) { return (0, object_1.isObject)(value); } ; /** * Validate that an attribute exists even if not filled. */ validatePresent(value, parameters, attribute) { return typeof (0, object_1.deepFind)(this.data, attribute) !== 'undefined'; } ; /** * Validate that an attribute is an integer. */ validateInteger(value, parameters, attribute) { if (validationRuleParser_1.default.hasRule(attribute, 'strict', this.rules) && typeof value !== 'number') { return false; } return (0, general_1.isInteger)(value); } ; /** * Validate that the attribute is a valid JSON string */ validateJson(value) { if (!value || typeof value !== 'string') { return false; } try { JSON.parse(value); } catch (e) { return false; } return true; } ; /** * Validate that an attribute is greater than another attribute. */ validateGt(value, parameters, attribute) { this.requireParameterCount(1, parameters, 'gt'); if (typeof value !== 'number' && typeof value !== 'string' && typeof value !== 'object') { throw 'The field under validation must be a number, string, array or object'; } const compartedToValue = (0, object_1.deepFind)(this.data, parameters[0]) || parameters[0]; if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) { return (0, general_1.getSize)(value, validationRuleParser_1.default.hasRule(attribute, (0, general_1.getNumericRules)(), this.rules)) > compartedToValue; } if ((0, general_1.sameType)(value, compartedToValue) === false) { throw 'The fields under validation must be of the same type'; } return (0, general_1.getSize)(value) > (0, general_1.getSize)(compartedToValue); } ; /** * Validate that an attribute is greater than or equal another attribute. */ validateGte(value, parameters, attribute) { this.requireParameterCount(1, parameters, 'gte'); if (typeof value !== 'number' && typeof value !== 'string' && typeof value !== 'object') { throw 'The field under validation must be a number, string, array or object'; } const compartedToValue = (0, object_1.deepFind)(this.data, parameters[0]) || parameters[0]; if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) { return (0, general_1.getSize)(value, validationRuleParser_1.default.hasRule(attribute, (0, general_1.getNumericRules)(), this.rules)) >= compartedToValue; } if ((0, general_1.sameType)(value, compartedToValue) === false) { throw 'The fields under validation must be of the same type'; } return (0, general_1.getSize)(value) >= (0, general_1.getSize)(compartedToValue); } ; /** * Validate that an attribute is less than another attribute. */ validateLt(value, parameters, attribute) { this.requireParameterCount(1, parameters, 'lt'); if (typeof value !== 'number' && typeof value !== 'string' && typeof value !== 'object') { throw 'The field under validation must be a number, string, array or object'; } const compartedToValue = (0, object_1.deepFind)(this.data, parameters[0]) || parameters[0]; if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) { return (0, general_1.getSize)(value, validationRuleParser_1.default.hasRule(attribute, (0, general_1.getNumericRules)(), this.rules)) < compartedToValue; } if ((0, general_1.sameType)(value, compartedToValue) === false) { throw 'The fields under validation must be of the same type'; } return (0, general_1.getSize)(value) < (0, general_1.getSize)(compartedToValue); } ; /** * Validate that an attribute is less than or equal another attribute. */ validateLte(value, parameters, attribute) { this.requireParameterCount(1, parameters, 'lte'); if (typeof value !== 'number' && typeof value !== 'string' && typeof value !== 'object') { throw 'The field under validation must be a number, string, array or object'; } const compartedToValue = (0, object_1.deepFind)(this.data, parameters[0]) || parameters[0]; if (!Array.isArray(compartedToValue) && isNaN(compartedToValue) === false) { return (0, general_1.getSize)(value, validationRuleParser_1.default.hasRule(attribute, (0, general_1.getNumericRules)(), this.rules)) <= compartedToValue; } if ((0, general_1.sameType)(value, compartedToValue) === false) { throw 'The fields under validation must be of the same type'; } return (0, general_1.getSize)(value) <= (0, general_1.getSize)(compartedToValue); } ; /** * Validate an attribute is contained within a list of values. */ validateIn(value, parameters) { this.requireParameterCount(1, parameters, 'in'); if (Array.isArray(value)) { for (let index = 0; index < value.length; index++) { if (typeof value[index] !== 'number' && typeof value[index] !== 'string') { return false; } } return value.filter(element => parameters.indexOf(element.toString()) === -1).length === 0; } ; if (typeof value !== 'number' && typeof value !== 'string') { return false; } return parameters.indexOf(value.toString()) !== -1; } ; /** * "Indicate" validation should pass if value is null * * Always returns true, just lets us put "nullable" in rules. */ validateNullable() { return true; } ; /** * Validate an attribute is not contained within a list of values. */ validateNotIn(value, parameters) { this.requireParameterCount(1, parameters, 'not_in'); const valuesToCheck = []; if (Array.isArray(value)) { for (let index = 0; index < value.length; index++) { if (typeof value[index] === 'number' || typeof value[index] === 'string') { valuesToCheck.push(value[index]); } } if (valuesToCheck.length === 0) { return true; } return valuesToCheck.filter(element => parameters.indexOf(element.toString()) !== -1).length === 0; } ; if (typeof value !== 'number' && typeof value !== 'string') { return true; } return parameters.indexOf(value.toString()) === -1; } ; /** * Always returns true - this method will be used in conbination with other rules */ validateStrict() { return true; } ; /** * Validate that an attribute is a valid URL. */ validateUrl(value) { if (typeof value !== 'string') { return false; } const pattern = new RegExp('^(https?:\\/\\/)?' + // protocol '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|localhost|' + // domain name '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator return pattern.test(value); } ; /** * Determine if a comparison passes between the given values. */ compareDates(value, parameter, operator, rule) { value = (0, date_1.toDate)(value); if (!value) { throw `Validation rule ${rule} requires the field under valation to be a date.`; } const compartedToValue = (0, date_1.toDate)((0, object_1.deepFind)(this.data, parameter) || parameter); if (!compartedToValue) { throw `Validation rule ${rule} requires the parameter to be a date.`; } return (0, general_1.compare)(value.getTime(), compartedToValue.getTime(), operator); } ; /** * Require a certain number of parameters to be present */ requireParameterCount(count, parameters, rule) { if (parameters.length < count) { throw `Validation rule ${rule} requires at least ${count} parameters.`; } } ; /** * Prepare the values for validation */ parseDependentRuleParameters(other, parameters) { let values = parameters.slice(1); if (other === null) { values = (0, general_1.convertValuesToNull)(values); } if (typeof other === 'number') { values = (0, general_1.convertValuesToNumber)(values); } if (typeof other === 'boolean') { values = (0, general_1.convertValuesToBoolean)(values); } return values; } } ; exports.default = validateAttributes;