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
JavaScript
'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;