UNPKG

@valkyriestudios/validator

Version:

A lightweight configurable javascript validator

627 lines (626 loc) 23.3 kB
"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();