@valkyriestudios/validator
Version:
A lightweight configurable javascript validator
627 lines (626 loc) • 23.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _Validator_plan, _Validator_schema;
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = exports.Validator = void 0;
const array_1 = require("@valkyriestudios/utils/array");
const boolean_1 = require("@valkyriestudios/utils/boolean");
const date_1 = require("@valkyriestudios/utils/date");
const deep_1 = require("@valkyriestudios/utils/deep");
const formdata_1 = require("@valkyriestudios/utils/formdata");
const function_1 = require("@valkyriestudios/utils/function");
const number_1 = require("@valkyriestudios/utils/number");
const object_1 = require("@valkyriestudios/utils/object");
const string_1 = require("@valkyriestudios/utils/string");
const equal_1 = require("@valkyriestudios/utils/equal");
const fnv1A_1 = require("@valkyriestudios/utils/hash/fnv1A");
const VR = __importStar(require("./functions/index"));
const RULE_STORE = {
alpha_num_spaces: VR.vAlphaNumSpaces,
alpha_num_spaces_multiline: VR.vAlphaNumSpacesMultiline,
array: array_1.isArray,
array_ne: array_1.isNeArray,
base64: VR.vBase64,
between: VR.vBetween,
between_inc: VR.vBetweenInclusive,
blob: VR.vBlob,
boolean: boolean_1.isBoolean,
color_hex: VR.vColorHex,
continent: VR.vContinent,
country: VR.vCountry,
country_alpha3: VR.vCountryAlpha3,
cron: VR.vCron,
date: date_1.isDate,
date_day: VR.vDateDay,
date_iso: VR.vDateISO,
date_string: VR.vDateString,
ean: VR.vEAN,
ean_8: VR.vEAN8,
ean_13: VR.vEAN13,
email: VR.vEmail,
equal_to: equal_1.equal,
false: VR.vFalse,
file: VR.vFile,
formdata: formdata_1.isFormData,
function: function_1.isFn,
async_function: function_1.isAsyncFn,
geo_latitude: VR.vGeoLatitude,
geo_longitude: VR.vGeoLongitude,
greater_than: VR.vGreaterThan,
greater_than_or_equal: VR.vGreaterThanOrEqual,
guid: VR.vGuid,
in: VR.vIn,
integer: number_1.isInt,
isbn: VR.vISBN,
isbn_10: VR.vISBN10,
isbn_13: VR.vISBN13,
less_than: VR.vLessThan,
less_than_or_equal: VR.vLessThanOrEqual,
literal: VR.vLiteral,
max: VR.vLessThanOrEqual,
min: VR.vGreaterThanOrEqual,
null: VR.vNull,
number: number_1.isNum,
object: object_1.isObject,
object_ne: object_1.isNeObject,
phone: VR.vPhone,
size: VR.vSize,
ssn: VR.vSSN,
string: string_1.isString,
string_ne: string_1.isNeString,
sys_mac: VR.vSysMac,
sys_ipv4: VR.vSysIPv4,
sys_ipv6: VR.vSysIPv6,
sys_ipv4_or_v6: VR.vSysIPv4_or_v6,
sys_port: VR.vSysPort,
time_zone: VR.vTimeZone,
true: VR.vTrue,
ulid: VR.vUlid,
url: VR.vUrl,
url_noquery: VR.vUrlNoQuery,
url_img: VR.vUrlImage,
url_vid: VR.vUrlVideo,
url_aud: VR.vUrlAudio,
url_med: VR.vUrlMedia,
uuid: VR.vUuid,
uuid_v1: VR.vUuidV1,
uuid_v2: VR.vUuidV2,
uuid_v3: VR.vUuidV3,
uuid_v4: VR.vUuidV4,
uuid_v5: VR.vUuidV5,
gt: VR.vGreaterThan,
gte: VR.vGreaterThanOrEqual,
lt: VR.vLessThan,
lte: VR.vLessThanOrEqual,
eq: equal_1.equal,
'?': VR.vUndefined,
};
const NOEXISTS = () => false;
const iterableDictHandler = (val) => {
if (!(0, object_1.isObject)(val))
return null;
const values = Object.values(val);
return { len: values.length, values };
};
const iterableArrayHandler = (val) => {
if (!Array.isArray(val))
return null;
return { len: val.length, values: val };
};
function getIterableConfig(val, dict) {
const unique = val.includes('unique');
const len = val.length;
const max_ix = val.indexOf('max:');
const min_ix = val.indexOf('min:');
const rslt = {
unique,
max: Number.MAX_SAFE_INTEGER,
min: -1,
handler: dict ? iterableDictHandler : iterableArrayHandler,
};
if (max_ix !== -1) {
const end_ix = val.indexOf('|', max_ix);
rslt.max = parseInt(val.slice(max_ix + 4, end_ix !== -1 ? end_ix : len), 10);
}
if (min_ix !== -1) {
const end_ix = val.indexOf('|', min_ix);
rslt.min = parseInt(val.slice(min_ix + 4, end_ix !== -1 ? end_ix : len), 10);
}
return rslt;
}
function parseRule(raw) {
let iterable = false;
let pos = 0;
const len = raw.length;
if (len > 0 && (raw[0] === '[' || raw[0] === '{')) {
const closingChar = raw[0] === '[' ? ']' : '}';
let endPos = -1;
for (let i = 1; i < len; i++) {
if (raw[i] === closingChar) {
endPos = i;
break;
}
}
if (endPos === -1)
throw new TypeError(`Iterable misconfiguration, verify rule config for ${raw}`);
iterable = getIterableConfig(raw.slice(0, endPos), raw[0] !== '[');
pos = endPos + 1;
}
const list = [];
while (pos < len) {
const partStart = pos;
while (pos < len && raw[pos] !== '|')
pos++;
const part = raw.slice(partStart, pos);
let colonPos = -1;
for (let i = 0; i < part.length; i++) {
if (part[i] !== ':')
continue;
colonPos = i;
break;
}
let ruleType;
let not = false;
let params = [];
if (colonPos === -1) {
ruleType = part;
}
else {
ruleType = part.slice(0, colonPos);
}
if (ruleType[0] === '!') {
not = true;
ruleType = ruleType.slice(1);
}
if (colonPos !== -1) {
const paramsPart = part.slice(colonPos + 1);
let pPos = 0;
const pLen = paramsPart.length;
while (pPos < pLen) {
const start = pPos;
while (pPos < pLen && paramsPart[pPos] !== ',')
pPos++;
let token = paramsPart.slice(start, pPos);
let extract = false;
if (token[0] === '<' && token[token.length - 1] === '>') {
token = token.slice(1, -1);
if (!(0, string_1.isNeString)(token))
throw new TypeError('Parameterization misconfiguration');
extract = true;
}
params.push([token, extract]);
pPos++;
}
if (ruleType === 'in' && params.length > 1) {
params = [[paramsPart.split(','), false]];
}
}
list.push({ type: ruleType, not, msg: (not ? 'not_' : '') + ruleType, params, params_length: params.length });
pos++;
}
return { nested: false, iterable, list, list_length: list.length };
}
function constructParams(rule_el, data) {
const { params_length, params } = rule_el;
const acc = new Array(params_length);
for (let i = 0; i < params_length; i++) {
const p = params[i];
acc[i] = !p[1] ? p[0] : (0, deep_1.deepGet)(data, p[0]);
}
return acc;
}
function validateField(cursor, rule, data, idx) {
const errors = [];
for (let i = 0; i < rule.list_length; i++) {
const rule_el = rule.list[i];
const rulefn = RULE_STORE[rule_el.type];
if (!rulefn) {
errors.push({ msg: 'rule_not_found', params: [rule_el.type] });
continue;
}
const params = constructParams(rule_el, data);
if (rulefn(cursor, ...params) === rule_el.not) {
errors.push({ msg: rule_el.msg, params, ...idx !== undefined && { idx } });
}
}
return { errors, is_valid: errors.length === 0 };
}
function validatePlan(data, plan) {
const errors = {};
let count = 0;
mainLoop: for (let i = 0; i < plan.length; i++) {
const group = plan[i];
const { key, rules, sometimes } = group;
const cursor = (0, deep_1.deepGet)(data, key);
if (cursor === undefined) {
if (!sometimes) {
count++;
errors[key] = [{ msg: 'not_found', params: [] }];
}
continue;
}
const group_errors = [];
for (let x = 0; x < rules.length; x++) {
const rule = rules[x];
let evaluation;
if (rule.nested) {
if (!(0, object_1.isObject)(cursor)) {
evaluation = [{ msg: 'not_object', params: [] }];
}
else {
const result = validatePlan(data, rule.plan);
if (result.is_valid)
continue mainLoop;
evaluation = result.errors;
}
}
else if (!rule.iterable) {
const result = validateField(cursor, rule, data);
if (result.is_valid)
continue mainLoop;
evaluation = result.errors;
}
else {
const iterable_data = rule.iterable.handler(cursor);
if (!iterable_data) {
evaluation = [{ msg: 'iterable', params: [] }];
}
else {
const { len, values } = iterable_data;
const error_cursor = [];
if (len < rule.iterable.min) {
error_cursor.push({ msg: 'iterable_min', params: [rule.iterable.min] });
}
else if (len > rule.iterable.max) {
error_cursor.push({ msg: 'iterable_max', params: [rule.iterable.max] });
}
else {
let unique_set = rule.iterable.unique ? new Set() : false;
for (let idx = 0; idx < len; idx++) {
const cursor_value = values[idx];
const eval_field = validateField(cursor_value, rule, data, idx);
if (!eval_field.is_valid)
error_cursor.push(...eval_field.errors);
if (unique_set) {
const hash = (0, fnv1A_1.fnv1A)(cursor_value);
if (unique_set.has(hash)) {
unique_set = false;
error_cursor.unshift({ msg: 'iterable_unique', params: [] });
}
else {
unique_set.add(hash);
}
}
}
}
if (error_cursor.length === 0)
continue mainLoop;
evaluation = error_cursor;
}
}
if (evaluation)
group_errors.push(evaluation);
}
const error = group_errors[0];
if ((0, array_1.isArray)(error)) {
if (group.rules.length > 1) {
const normalized = [];
for (let x = 0; x < group_errors.length; x++) {
normalized.push(group_errors[x]);
}
errors[key] = normalized;
}
else {
errors[key] = error;
}
count++;
}
else {
Object.assign(errors, error);
count += Object.keys(error).length;
}
}
return { is_valid: count === 0, count, errors };
}
function checkRule(cursor, rule, data) {
const { iterable, list_length, list } = rule;
if (!iterable) {
for (let i = 0; i < list_length; i++) {
const rule_el = list[i];
if ((RULE_STORE[rule_el.type] || NOEXISTS)(cursor, ...constructParams(rule_el, data)) === rule_el.not)
return false;
}
return true;
}
const iterable_data = iterable.handler(cursor);
if (!iterable_data)
return false;
const { len, values } = iterable_data;
if (len < iterable.min || len > iterable.max)
return false;
const unique_set = new Set();
const param_acc = [];
let cursor_value;
for (let idx = 0; idx < len; idx++) {
cursor_value = values[idx];
for (let i = 0; i < list_length; i++) {
const rule_el = list[i];
if (!param_acc[i])
param_acc[i] = constructParams(rule_el, data);
if ((RULE_STORE[rule_el.type] || NOEXISTS)(cursor_value, ...param_acc[i]) === rule_el.not)
return false;
}
if (iterable.unique) {
unique_set.add((0, fnv1A_1.fnv1A)(cursor_value));
if (unique_set.size !== (idx + 1))
return false;
}
}
return true;
}
function checkPlan(data, plan) {
mainloop: for (let i = 0; i < plan.length; i++) {
const { key, sometimes, rules } = plan[i];
const cursor = (0, deep_1.deepGet)(data, key);
if (cursor === undefined) {
if (!sometimes)
return false;
continue;
}
for (let x = 0; x < rules.length; x++) {
const rule = rules[x];
if (!rule.nested) {
if (checkRule(cursor, rule, data))
continue mainloop;
}
else if ((0, object_1.isObject)(cursor) && checkPlan(data, rule.plan)) {
continue mainloop;
}
}
return false;
}
return true;
}
function recursor(plan, val, key) {
if (!val)
throw new TypeError('Invalid rule value');
if ((0, string_1.isString)(val)) {
const sometimes = val[0] === '?';
plan.push({
key,
sometimes,
rules: [parseRule(sometimes ? val.slice(1) : val)],
});
}
else if ((0, array_1.isArray)(val)) {
let sometimes = false;
const rules = [];
for (let i = 0, len = val.length; i < len; i++) {
const branch = val[i];
if (branch === '?') {
sometimes = true;
}
else if (branch) {
if ((0, string_1.isString)(branch)) {
rules.push(parseRule(branch));
}
else if ((0, object_1.isObject)(branch)) {
const nested_plan = [];
recursor(nested_plan, branch, key);
rules.push({ nested: true, plan: nested_plan });
}
}
else {
throw new TypeError('Invalid Conditional group alternative');
}
}
if (rules.length) {
plan.push({ key, sometimes, rules });
}
else {
throw new TypeError('Invalid rule value');
}
}
else if ((0, object_1.isObject)(val)) {
for (const val_key in val) {
recursor(plan, val[val_key], key ? key + '.' + val_key : val_key);
}
}
else {
throw new TypeError('Invalid rule value');
}
}
function freezeStore(dict) {
const store = {};
for (const key in dict)
store[key] = dict[key];
return Object.freeze(store);
}
let FROZEN_RULE_STORE = freezeStore(RULE_STORE);
class Validator {
constructor(schema) {
_Validator_plan.set(this, void 0);
_Validator_schema.set(this, void 0);
const raw = ((0, object_1.isObject)(schema)
? [schema]
: (0, array_1.isArray)(schema)
? schema
: []);
if (!raw.length)
throw new TypeError('Validator@ctor: Schema needs to be an object or array of objects');
const plans = [];
for (let i = 0; i < raw.length; i++) {
const plan = [];
recursor(plan, raw[i], '');
plans.push(plan);
}
if (plans.length > 1) {
__classPrivateFieldSet(this, _Validator_plan, { type: 'multi', plans: plans }, "f");
__classPrivateFieldSet(this, _Validator_schema, (0, deep_1.deepFreeze)(raw), "f");
}
else {
__classPrivateFieldSet(this, _Validator_plan, { type: 'single', plan: plans[0] }, "f");
__classPrivateFieldSet(this, _Validator_schema, (0, deep_1.deepFreeze)(raw[0]), "f");
}
}
get schema() {
return JSON.parse(JSON.stringify(__classPrivateFieldGet(this, _Validator_schema, "f")));
}
check(raw) {
const data = raw instanceof FormData
? (0, formdata_1.toObject)(raw)
: raw;
if (!(0, object_1.isObject)(data))
return false;
if (__classPrivateFieldGet(this, _Validator_plan, "f").type === 'multi') {
for (let i = 0; i < __classPrivateFieldGet(this, _Validator_plan, "f").plans.length; i++) {
const plan = __classPrivateFieldGet(this, _Validator_plan, "f").plans[i];
if (checkPlan(data, plan))
return true;
}
return false;
}
else {
return checkPlan(data, __classPrivateFieldGet(this, _Validator_plan, "f").plan);
}
}
checkForm(raw) {
if (!(raw instanceof FormData))
return false;
const data = (0, formdata_1.toObject)(raw);
return this.check(data) ? data : false;
}
validate(raw) {
const data = raw instanceof FormData
? (0, formdata_1.toObject)(raw)
: raw;
if (!(0, object_1.isObject)(data))
return { is_valid: false, errors: 'NO_DATA', count: 1 };
if (__classPrivateFieldGet(this, _Validator_plan, "f").type === 'multi') {
const results = [];
for (let i = 0; i < __classPrivateFieldGet(this, _Validator_plan, "f").plans.length; i++) {
const result = validatePlan(data, __classPrivateFieldGet(this, _Validator_plan, "f").plans[i]);
if (result.is_valid)
return result;
results.push(result);
}
return results[0];
}
else {
return validatePlan(data, __classPrivateFieldGet(this, _Validator_plan, "f").plan);
}
}
static get rules() {
return FROZEN_RULE_STORE;
}
static extend(obj) {
if (!(0, object_1.isObject)(obj))
throw new Error('Invalid extension');
const schemas_map = {};
for (const [key, val] of Object.entries(obj)) {
if (typeof key !== 'string' || !/^[A-Za-z_0-9-]+$/.test(key))
throw new Error('Invalid extension');
if (val instanceof RegExp ||
((0, array_1.isNeArray)(val) && val.filter(el => (0, string_1.isNeString)(el) || (0, number_1.isNum)(el)).length === val.length) ||
((0, function_1.isFn)(val) && !(0, function_1.isAsyncFn)(val)))
continue;
if ((0, object_1.isNeObject)(val)) {
try {
schemas_map[key] = new Validator(val);
continue;
}
catch {
throw new Error('Invalid extension');
}
}
throw new Error('Invalid extension');
}
for (const [key, value] of Object.entries(obj)) {
let builtValue;
if (value instanceof RegExp) {
builtValue = function (val) {
return typeof val === 'string' && this.rgx.test(val);
};
builtValue.rgx = new RegExp(value);
builtValue = builtValue.bind(builtValue);
}
else if (Array.isArray(value)) {
builtValue = function (val) {
return this.set.has(val);
};
builtValue.set = new Set([...value]);
builtValue = builtValue.bind(builtValue);
}
else if ((0, object_1.isObject)(value)) {
builtValue = function (val) {
return this.v.check(val);
};
builtValue.v = schemas_map[key];
builtValue = builtValue.bind(builtValue);
}
else {
builtValue = value;
}
RULE_STORE[key] = builtValue;
}
FROZEN_RULE_STORE = freezeStore(RULE_STORE);
return this;
}
static create(schema) {
if ((0, array_1.isNeArray)(schema) && schema[0] instanceof Validator) {
const normalized = [];
for (let i = 0; i < schema.length; i++)
normalized.push(schema[i].schema);
return new Validator(normalized);
}
else {
return new Validator(schema);
}
}
}
exports.Validator = Validator;
exports.default = Validator;
_Validator_plan = new WeakMap(), _Validator_schema = new WeakMap();