Skip to main content
Module

x/valivar/dist/valivar.esm.js

Javascript/Typescript schema-based validation and sanitation
Latest
File
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595
/**************************************************************************************** MODIFIED FROM* Title: component/component-type* Author: Component Org* Date: August 28, 2020* Code version: 1.2.1* Availability: https://github.com/component/type****************************************************************************************/const toString = Object.prototype.toString;const funToString = Function.prototype.toString;const localGlobal = { Buffer: typeof Buffer !== 'undefined' ? Buffer : ArrayBuffer, // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore globalThis: typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : {}};/** * Return the type of `val` as a string. * * @param {Mixed} val * @return {String} * @public */
function getType(val) { switch (toString.call(val)) { case '[object Date]': return 'date';
case '[object RegExp]': return 'regexp';
case '[object Arguments]': return 'arguments';
case '[object Array]': return 'array';
case '[object Error]': return 'error';
case '[object Map]': return 'map'; }
if (val === null) return 'null'; if (val === undefined) return 'undefined'; if (typeof val === 'number' && isNaN(val)) return 'nan'; if (typeof val === 'object' && isElement(val)) return 'element'; if (typeof val === 'object' && isNode(val)) return 'node'; if (typeof val === 'object' && isBuffer(val)) return 'buffer';
if (isWholeObject(val)) { val = val?.valueOf ? val?.valueOf() : Object.prototype.valueOf.apply(val); }
if (typeof val === 'function' && funToString.call(val).substr(0, 5) === 'class') return 'class'; return typeof val;
function isWholeObject(obj) { return typeof obj === 'object' && obj !== null && !!Object.keys(obj).length; }
function isBuffer(obj) { return !!( // Does not support Safari 5-7 (missing Object.prototype.constructor) // Accepted as Safari 5-7 (Mobile & Desktop) is at < 0.17% usage // https://caniuse.com/usage-table obj instanceof localGlobal.Buffer); } // HTML Type Checking from https://stackoverflow.com/questions/384286/how-do-you-check-if-a-javascript-object-is-a-dom-object //Returns true if it is a DOM node

function isNode(o) { const globalKey = 'Node'; return Object.prototype.hasOwnProperty.call(localGlobal.globalThis, globalKey) ? o instanceof localGlobal.globalThis[globalKey] : o && isWholeObject(o) && typeof o.nodeType === 'number' && typeof o.nodeName === 'string'; } //Returns true if it is a DOM element

function isElement(o) { const globalKey = 'HTMLElement'; return Object.prototype.hasOwnProperty.call(localGlobal.globalThis, globalKey) ? o instanceof localGlobal.globalThis[globalKey] : o && isWholeObject(o) && o.nodeType === 1 && typeof o.nodeName === 'string'; }}
/** * Assign given key and value (or object) to given object * * @private */function assign(key, val, obj) { if (typeof key === 'string') { obj[key] = val; return; }
if (typeof key === 'object') { Object.keys(key).forEach(k => obj[k] = key[k]); }}/** * Join `path` with `prefix` * * @private */
function join(path, prefix) { return prefix ? `${prefix.toString()}.${path.toString()}` : path.toString();}function isWholeObject(obj) { return typeof obj === 'object' && obj !== null && !!Object.keys(obj).length;}function isLimitedKey(prop) { return typeof prop === 'string' || typeof prop === 'number';}function isIntegerLike(prop) { return typeof prop !== 'symbol' && !isNaN(parseInt('' + prop, 10));}function isRule(obj) { return typeof obj === 'function' || typeof obj === 'object' || typeof obj === 'string' || Array.isArray(obj) || typeof obj === 'boolean';}function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop);}function hasConstructor(obj) { return typeof obj['constructor'] === 'function';}function isSomething(obj) { return typeof obj === 'object' || typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean';}/*** @private*/
function isSafe(obj, prop) { if (isObject(obj)) { return obj[prop] === undefined || hasOwnProperty(obj, prop); }
if (Array.isArray(obj)) { return !isNaN(parseInt('' + prop, 10)); }
return false;}/*** @private*/
function isObject(obj) { return Object.prototype.toString.call(obj) === '[object Object]';}/*** @private*/
function isRecord(obj) { return typeof obj === 'object' && obj !== null;}
/**************************************************************************************** Title: eivindfjeldstad/dot* Author: Eivind Fjeldstad* Date: August 28, 2020* Code version: 1.0.3* Availability: https://github.com/eivindfjeldstad/dot****************************************************************************************//** * Get and set points in an object by their 'dot' path */
const dot = { name: 'Dot',
/** * Set given `path` * * @param {Object} obj * @param {String} path * @param {Mixed} val * @return {Object} * @public */ set(obj, path, val) { const segs = path.split('.'); const attr = segs.pop(); const src = obj; let currentLayer = obj;
for (let i = 0; i < segs.length; i++) { const seg = segs[i];
if (isSafe(currentLayer, seg)) { if (Array.isArray(currentLayer) && isIntegerLike(seg)) { currentLayer[seg] = currentLayer[seg] || []; currentLayer = currentLayer[seg]; } else { const overCurrent = currentLayer; overCurrent[seg] = overCurrent[seg] || {}; currentLayer = overCurrent[seg]; } } else { return src; } }
if (attr !== null && attr !== undefined && isSafe(currentLayer, attr)) { if (Array.isArray(currentLayer) && isIntegerLike(attr)) { currentLayer[attr] = val; } else if (isObject(currentLayer)) { currentLayer[attr] = val; } }
return src; },
/** * Get given `path` * * @param {Object} obj * @param {String} path * @return {Mixed} * @public */ get(obj, path) { const segs = path.split('.'); const attr = segs.pop(); let currentLayer = obj;
for (let i = 0; i < segs.length; i++) { const seg = segs[i];
if (isSafe(currentLayer, seg)) { if (Array.isArray(currentLayer) && isIntegerLike(seg)) { currentLayer = currentLayer[seg]; } else { const overCurrent = currentLayer; currentLayer = overCurrent[seg]; } } else { return; } }
if (attr !== null && attr !== undefined) { if (Array.isArray(currentLayer) && isIntegerLike(attr)) { return currentLayer[attr]; } else if (isObject(currentLayer)) { return currentLayer[attr]; } else { return; } } else { return; } },
/** * Delete given `path` * * @param {Object} obj * @param {String} path * @return {Mixed} * @public */ delete(obj, path) { const segs = path.split('.'); const attr = segs.pop(); let reObj = obj;
for (let i = 0; i < segs.length; i++) { const seg = segs[i]; if (!isRecord(reObj) || !reObj[seg]) return;
if (isSafe(reObj, seg)) { reObj = reObj[seg]; } else { return; } }
if (attr === null || attr === undefined || !isSafe(reObj, attr)) return;
if (Array.isArray(reObj)) { reObj.splice(parseInt(attr), 1); } else { delete reObj[attr]; } }
};const typeOf = getType;/** * Enumerate all permutations of `path`, replacing $ with array indices and * with object indices * * @private */
function enumerate(path, obj, callback) { const parts = path.split(/\.[$*](?=\.|$|\*)/); const first = parts.shift(); const arr = dot.get(obj, first || '');
if (!parts.length) { return callback(first || '', arr); }
if (!Array.isArray(arr)) { if (typeOf(arr) === 'object') { const keys = Object.keys(arr);
for (let i = 0; i < keys.length; i++) { const current = join(keys[i], first); const next = current + parts.join('.*'); enumerate(next, obj, callback); } }
return; }
for (let i = 0; i < arr.length; i++) { const current = join(i, first); const next = current + parts.join('.$'); enumerate(next, obj, callback); }}/** * Walk object and call `callback` with path and prop name * * @private */
function walk(obj, callback, path, prop) { const type = typeOf(obj);
if (type === 'array') { const localObj = obj; localObj.forEach((v, i) => walk(v, callback, join(i, path), join('$', prop))); return; }
if (type !== 'object') { return; }
const localObj = obj;
for (const [key, val] of Object.entries(localObj)) { const newPath = join(key, path); const newProp = join(key, prop); const newCatchProp = join('*', prop);
if (callback(newPath, newCatchProp, true)) { walk(val, callback, newPath, newCatchProp); } else if (callback(newPath, newProp)) { walk(val, callback, newPath, newProp); } }}
/**************************************************************************************** MODIFIED FROM* Title: eivindfjeldstad/typecast* Author: Eivind Fjeldstad* Date: August 28, 2020* Code version: 1.0.1* Availability: https://github.com/eivindfjeldstad/typecast****************************************************************************************/
/** * @module typecast * @category Bonus Modules */
/** * Cast given `val` to `type` * @name typecast * @property {casters} casters * @param {Mixed} val * @param {String} type * @public */const typecast = function (val, type) { const fn = typecast.casters[type]; if (typeof fn !== 'function') throw new Error('cannot cast to ' + type); return fn(val);};
const casters = { /** * Cast `val` to `String` * @alias casters.string * @memberof! typecast * @param {Mixed} val * @returns {string} * @public */ string: function (val) { if (val === null || val === undefined) return '';
if (typeof val === 'object' && val !== null && Object.entries(val).length) { return JSON.stringify(val); }
return String(val).toString(); },
/** * Cast `val` to `Number` * @alias casters.number * @memberof! typecast * @param {Mixed} val * @returns {number} * @public */ number: function (val) { const num = parseFloat(String(val).toString()); return isNaN(num) ? 0 : num; },
/** * Cast `val` to a`Date` * @alias casters.date * @memberof! typecast * @param {Mixed} val * @returns {Date} * @public */ date: function (val) { if (!(typeof val === 'string' || typeof val === 'number' || val instanceof Date)) { return new Date(0); } else { const date = new Date(val); return isNaN(date.valueOf()) ? new Date(0) : date; } },
/** * Cast `val` to `Array` * @alias casters.array * @memberof! typecast * @param {Mixed} val * @returns {Array} * @public */ array: function (val) { if (val === null || val === undefined) return []; if (val instanceof Array) return val; if (typeof val !== 'string') return [val]; const arr = val.split(',');
for (let i = 0; i < arr.length; i++) { arr[i] = arr[i].trim(); }
return arr; },
/** * Cast `val` to `Boolean` * @alias casters.boolean * @memberof! typecast * @param {Mixed} val * @returns {boolean} * @public */ boolean: function (val) { return !!val && val !== 'false' && val !== '0'; },
/** * Cast `val` to `Object` * @alias casters.object * @memberof! typecast * @param {Mixed} val * @returns {object} * @public */ object: function (val) { if (val === null || val === undefined) return {}; if (Array.isArray(val)) return Object.fromEntries(Object.entries(val)); if (typeof val === 'object' && val !== null) return val; if (typeof val !== 'string') return { value: val }; let obj = {};
try { obj = JSON.parse(val); } catch (error) { obj = { value: val }; }
return obj; }};typecast.casters = casters;Object.defineProperty(typecast, 'name', { value: 'Typecast'});
/*! *****************************************************************************Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for anypurpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITHREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITYAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROMLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OROTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE ORPERFORMANCE OF THIS SOFTWARE.***************************************************************************** */
function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r;}
function __metadata(metadataKey, metadataValue) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);}
const nonenumerable = (target, name, desc) => { if (desc) { desc.enumerable = false; return desc; }
Object.defineProperty(target, name, { set(value) { Object.defineProperty(this, name, { value, writable: true, configurable: true }); },
configurable: true });};
/** * Custom errors. * */
class ValidationError extends Error { constructor(message, path) { super(message); this.path = path; this.expose = true; this.status = 400;
if (Error.captureStackTrace) { Error.captureStackTrace(this, ValidationError); } }
}
__decorate([nonenumerable, __metadata("design:type", Object)], ValidationError.prototype, "path", void 0);
__decorate([nonenumerable, __metadata("design:type", Boolean)], ValidationError.prototype, "expose", void 0);
__decorate([nonenumerable, __metadata("design:type", Number)], ValidationError.prototype, "status", void 0);
function isValidationFunctionArr(arr) { return Array.isArray(arr) && typeof arr[0] === 'function';}/** * A property instance gets returned whenever you call `schema.path()`. * Properties are also created internally when an object is passed to the Schema constructor. * * @param {String} name - the name of the property * @param {Schema} schema - parent schema */

class Property { constructor(name, schema) { this.name = name; this.registry = {}; this._schema = schema; this._type = null; this.messages = {}; } /** * Registers messages. * * @example * prop.message('something is wrong') * prop.message({ required: 'thing is required.' }) * * @param {Object|String} messages * @return {Property} */

message(messages) { if (typeof messages === 'string') { messages = { default: messages }; }
const entries = Object.entries(messages);
for (const [key, val] of entries) { this.messages[key] = val; }
return this; } /** * Mount given `schema` on current path. * * @example * const user = new Schema({ email: String }) * prop.schema(user) * * @param {Schema} schema - the schema to mount * @return {Property} */

schema(schema) { this._schema.path(this.name, schema);
return this; } /** * Validate using named functions from the given object. * Error messages can be defined by providing an object with * named error messages/generators to `schema.message()` * * The message generator receives the value being validated, * the object it belongs to and any additional arguments. * * @example * const schema = new Schema() * const prop = schema.path('some.path') * * schema.message({ * binary: (path, ctx) => `${path} must be binary.`, * bits: (path, ctx, bits) => `${path} must be ${bits}-bit` * }) * * prop.use({ * binary: (val, ctx) => /^[01]+$/i.test(val), * bits: [(val, ctx, bits) => val.length == bits, 32] * }) * * @param {Object} fns - object with named validation functions to call * @return {Property} */

use(fns) { Object.keys(fns).forEach(name => { const arr = fns[name];
if (isValidationFunctionArr(arr)) { const [fn, ...args] = arr;
this._register(name, args, fn); } else { this._register(name, [], arr); } }); return this; } /** * Registers a validator that checks for presence. * * @example * prop.required() * * @param {Boolean} [bool] - `true` if required, `false` otherwise * @return {Property} */

required(bool = true) { return this._register('required', [bool]); } /** * Registers a validator that checks if a value is of a given `type` * * @example * prop.type(String) * * @example * prop.type('string') * * @param {String|Function} type - type to check for * @return {Property} */

type(type) { this._type = type; return this._register('type', [type]); } /** * Convenience method for setting type to `String` * * @example * prop.string() * * @return {Property} */

string() { return this.type(String); } /** * Convenience method for setting type to `Number` * * @example * prop.number() * * @return {Property} */

number() { return this.type(Number); } /** * Convenience method for setting type to `Array` * * @example * prop.array() * * @return {Property} */

array() { return this.type(Array); } /** * Convenience method for setting type to `Object` * * @example * prop.object() * * @return {Property} */

object() { return this.type(Object); } /** * Convenience method for setting type to `Date` * * @example * prop.date() * * @return {Property} */

date() { return this.type(Date); } /** * Registers a validator that checks length. * * @example * prop.length({ min: 8, max: 255 }) * prop.length(10) * * @param {Object|Number} rules - object with `.min` and `.max` properties or a number * @param {Number} rules.min - minimum length * @param {Number} rules.max - maximum length * @return {Property} */

length(rules) { return this._register('length', [rules]); } /** * Registers a validator that checks size. * * @example * prop.size({ min: 8, max: 255 }) * prop.size(10) * * @param {Object|Number} rules - object with `.min` and `.max` properties or a number * @param {Number} rules.min - minimum size * @param {Number} rules.max - maximum size * @return {Property} */

size(rules) { return this._register('size', [rules]); } /** * Registers a validator for enums. * * @example * prop.enum(['cat', 'dog']) * * @param {Array} rules - allowed values * @return {Property} */

enum(enums) { return this._register('enum', [enums]); } /** * Registers a validator that checks if a value matches given `regexp`. * * @example * prop.match(/some\sregular\sexpression/) * * @param {RegExp} regexp - regular expression to match * @return {Property} */

match(regexp) { return this._register('match', [regexp]); } /** * Registers a validator that checks each value in an array against given `rules`. * * @example * prop.each({ type: String }) * prop.each([{ type: Number }]) * prop.each({ things: [{ type: String }]}) * prop.each(schema) * * @param {Array|Object|Schema|Property} rules - rules to use * @return {Property} */

each(rules) { this._schema.path(join('$', this.name), rules);
return this; } /** * Registers paths for array elements on the parent schema, with given array of rules. * * @example * prop.elements([{ type: String }, { type: Number }]) * * @param {Array} arr - array of rules to use * @return {Property} */

elements(arr) { arr.forEach((rules, i) => { this._schema.path(join(i, this.name), rules); }); return this; } /** * Registers all properties from the given object as nested properties * * @example * prop.properties({ * name: String, * email: String * }) * * @param {Object} props - properties with rules * @return {Property} */

properties(props) { for (const [prop, rule] of Object.entries(props)) { this._schema.path(join(prop, this.name), rule); }
return this; } /** * Proxy method for schema path. Makes chaining properties together easier. * * @example * schema * .path('name').type(String).required() * .path('email').type(String).required() * */

path(path, rules) { return this._schema.path(path, rules); } /** * Typecast given `value` * * @example * prop.type(String) * prop.typecast(123) // => '123' * * @param {Mixed} value - value to typecast * @return {Mixed} */

typecast(value) { const schema = this._schema; let type = this._type; if (!type) return value;
if (typeof type === 'function') { type = type.name; }
const cast = schema.typecasters[type] || typeof type === 'string' && schema.typecasters[type.toLowerCase()];
if (typeof cast !== 'function') { throw new Error(`Typecasting failed: No typecaster defined for ${type.toString()}.`); }
return cast(value); } /** * Validate given `value` * * @example * prop.type(Number) * assert(prop.validate(2) == null) * assert(prop.validate('hello world') instanceof Error) * * @param {Mixed} value - value to validate * @param {Object} ctx - the object containing the value * @param {String} [path] - path of the value being validated * @return {ValidationError} */

validate(value, ctx, path = this.name) { const types = Object.keys(this.registry);
for (const type of types) { const err = this._run(type, value, ctx, path);
if (err) return err; }
return null; } /** * Run validator of given `type` * * @param {String} type - type of validator * @param {Mixed} value - value to validate * @param {Object} ctx - the object containing the value * @param {String} path - path of the value being validated * @return {ValidationError} * @private */

_run(type, value, ctx, path) { if (!this.registry[type]) return; const isValidator = this._isValidator; const schema = this._schema; const { args, fn } = this.registry[type]; let validator = false; let valid = false;
if (fn) { validator = fn; } else if (isValidator.call(this, type)) { validator = schema.validators[type]; }
if (validator) { valid = validator(value, ctx, ...args, path); }
if (!valid) return this._error(type, ctx, args, path); } /** * Register validator * * @param {String} type - type of validator * @param {Array} args - argument to pass to validator * @param {Function} [fn] - custom validation function to call * @return {Property} * @private */

_register(type, args, fn) { this.registry[type] = { args, fn }; return this; } /** * Create an error * * @param {String} type - type of validator * @param {Object} ctx - the object containing the value * @param {Array} args - arguments to pass * @param {String} path - path of the value being validated * @return {ValidationError} * @private */

_error(type, ctx, args, path) { const schema = this._schema; const isMessage = this._isMessage; let message = this.messages[type] || this.messages.default;
if (!message) { if (isMessage.call(this, type)) { message = schema.messages[type]; } else { message = schema.messages.default; } }
if (typeof message === 'function') { message = message(path, ctx, ...args); }
return new ValidationError(message.toString(), path); }
_isValidator(fnName) { const schema = this._schema; const isProp = Object.prototype.hasOwnProperty.call(schema.validators, fnName); return isProp && typeof schema.validators[fnName] === 'function'; }
_isMessage(fnName) { const schema = this._schema; const isProp = Object.prototype.hasOwnProperty.call(schema.messages, fnName); return isProp && typeof schema.messages[fnName] === 'function' || typeof schema.messages[fnName] === 'string'; }
}
const Messages = { // Type message type(prop, ctx, type) { if (typeof type === 'function') { type = type.name; }
return `${prop} must be of type ${type}.`; },
// Required message required(prop) { return `${prop} is required.`; },
// Match message match(prop, ctx, regexp) { return `${prop} must match ${regexp}.`; },
// Length message length(prop, ctx, len) { if (typeof len === 'number') { return `${prop} must have a length of ${len}.`; }
const { min, max } = len;
if (min && max) { return `${prop} must have a length between ${min} and ${max}.`; }
if (max) { return `${prop} must have a maximum length of ${max}.`; }
if (min) { return `${prop} must have a minimum length of ${min}.`; }
return `${prop} must have a valid length`; },
// Size message size(prop, ctx, size) { if (typeof size === 'number') { return `${prop} must have a size of ${size}.`; }
const { min, max } = size;
if (min !== undefined && max !== undefined) { return `${prop} must be between ${min} and ${max}.`; }
if (max !== undefined) { return `${prop} must be less than ${max}.`; }
if (min !== undefined) { return `${prop} must be greater than ${min}.`; }
return `${prop} must have a valid size`; },
// Enum message enum(prop, ctx, enums) { const copy = enums.slice(); const last = copy.pop(); return `${prop} must be either ${copy.join(', ')} or ${last?.toString()}.`; },
// Illegal property illegal(prop) { return `${prop} is not allowed.`; },
// Default message default(prop) { return `Validation failed for ${prop}.`; }
};
function compareMagnitudes(len, length) { if ('min' in len) { if (typeof len.min === 'string') len.min = parseInt(len.min); if (typeof len.min === 'number' && length < len.min) return false; }
if ('max' in len) { if (typeof len.max === 'string') len.max = parseInt(len.max); if (typeof len.max === 'number' && length > len.max) return false; }
return true;}/** * Default validators. * * @private */

const Validators = { /** * Validates presence. * * @param {Mixed} value - the value being validated * @param {Object} ctx - the object being validated * @param {Bolean} required * @return {Boolean} */ required(value, ctx, required) { if (required === false) return true; return value !== null && value !== undefined && value !== ''; },
/** * Validates type. * * @param {Mixed} value - the value being validated * @param {Object} ctx - the object being validated * @param {String|Function} name name of the type or a constructor * @return {Boolean} */ type(value, ctx, name) { if (value === null || value === undefined) return true;
if (typeof name === 'function' && value !== null && isSomething(value) && hasConstructor(value)) { return value['constructor'] === name; }
return getType(value) === name; },
/** * Validates length. * * @param {String} value the string being validated * @param {Object} ctx the object being validated * @param {Object|Number} rules object with .min and/or .max props or a number * @param {Number} [rules.min] - minimum length * @param {Number} [rules.max] - maximum length * @return {Boolean} */ length(value, ctx, len) { if (value === null || value === undefined) return true;
if (typeof len === 'number') { return value.length === len; }
return compareMagnitudes(len, value.length); },
/** * Validates size. * * @param {Number} value the number being validated * @param {Object} ctx the object being validated * @param {Object|Number} size object with .min and/or .max props or a number * @param {String|Number} [size.min] - minimum size * @param {String|Number} [size.max] - maximum size * @return {Boolean} */ size(value, ctx, size) { if (value === null || value === undefined) return true;
if (typeof size === 'number') { return value === size; }
return compareMagnitudes(size, value); },
/** * Validates enums. * * @param {String} value the string being validated * @param {Object} ctx the object being validated * @param {Array} enums array with allowed values * @return {Boolean} */ enum(value, ctx, enums) { if (value === null || value === undefined) return true; return enums.includes(value); },
/** * Validates against given `regexp`. * * @param {String} value the string beign validated * @param {Object} ctx the object being validated * @param {RegExp} regexp the regexp to validate against * @return {Boolean} */ match(value, ctx, regexp) { if (value === null || value === undefined) return true; return regexp.test(value); }
};
const dot$1 = dot;const typecast$1 = typecast;/** * @module Schema */
/** * A Schema defines the structure that objects should be validated against. * @example * const post = new Schema({ * title: { * type: String, * required: true, * length: { min: 1, max: 255 } * }, * content: { * type: String, * required: true * }, * published: { * type: Date, * required: true * }, * keywords: [{ type: String }] * }) * * @param {Object} [obj] - schema definition * @param {Object} [opts] - options * @param {Boolean} [opts.typecast=false] - typecast values before validation * @param {Boolean} [opts.strip=true] - strip properties not defined in the schema * @param {Boolean} [opts.strict=false] - validation fails when object contains properties not defined in the schema */
class Schema { constructor(obj = {}, opts = {}) { this.opts = opts; this.hooks = []; this.props = {}; this.messages = Object.assign({}, Messages); this.validators = Object.assign({}, Validators); this.typecasters = Object.assign({}, typecast$1.casters); Object.keys(obj).forEach(k => this.path(k, obj[k])); } /** * Create or update `path` with given `rules`. * * @example * const schema = new Schema() * schema.path('name.first', { type: String }) * schema.path('name.last').type(String).required() * * @param {String} path - full path using dot-notation * @param {Object|Array|String|Schema|Property} [rules] - rules to apply * @return {Property} */

path(path, rules) { const parts = path.split('.'); const suffix = parts.pop(); const prefix = parts.join('.'); // Make sure full path is created
if (prefix) { this.path(prefix); } // Array index placeholder

if (suffix === '$') { this.path(prefix).type(Array); } // Catchall Object placeholder

if (suffix === '*') { this.path(prefix).type(Object); } // Nested schema

if (rules instanceof Schema) { rules.hook((k, v) => this.path(join(k, path), v)); return this.path(path, rules.props); } // Return early when given a `Property`

if (rules instanceof Property) { this.props[path] = rules; // Notify parents if mounted
this.propagate(path, rules); return rules; }
const prop = this.props[path] || new Property(path, this); this.props[path] = prop; // Notify parents if mounted
this.propagate(path, prop); // No rules?
if (!rules) return prop; // type shorthand // `{ name: String }`
if (typeof rules === 'string' || typeof rules === 'function') { prop.type(rules); return prop; } // Allow arrays to be defined implicitly: // `{ keywords: [String] }` // `{ keyVal: [[String, Number]] }`

if (Array.isArray(rules)) { prop.type(Array);
if (rules.length === 1) { if (isRule(rules[0])) { prop.each(rules[0]); } } else { if (rules.every(x => isRule(x))) { prop.elements(rules); } }
return prop; }
const keys = Object.keys(rules); let nested = false; // Check for nested objects
for (const key of keys) { if (isWholeObject(prop) && typeof prop[key] === 'function') continue; prop.type(Object); nested = true; break; }
keys.forEach(key => { const rule = rules[key];
if (isRule(rule)) { if (nested) { return this.path(join(key, path), rule); }
if (isWholeObject(prop)) { const pathFn = prop[key];
if (typeof pathFn === 'function') { pathFn.call(prop, rule); } } } }); return prop; } /** * Typecast given `obj`. * * @param {Object} obj - the object to typecast * @return {Schema} * @private */

typecast(obj) { for (const [path, prop] of Object.entries(this.props)) { if (isWholeObject(obj)) { enumerate(path, obj, (key, value) => { if (value === null || value === undefined) return; const cast = prop.typecast(value); if (cast === value) return; dot$1.set(obj, key, cast); }); } }
return this; } /** * Strip all keys not defined in the schema * * @param {Object} obj - the object to strip * @param {String} [prefix] * @return {Schema} * @private */

strip(obj) { walk(obj, (path, prop, isCatchall = false) => { if (isLimitedKey(prop) && this.props[prop]) return true; if (isCatchall) return false; if (isWholeObject(obj)) dot$1.delete(obj, path); return false; }); return this; } /** * Create errors for all properties that are not defined in the schema * * @param {Object} obj - the object to check * @return {Schema} * @private */

enforce(obj) { const errors = []; walk(obj, (path, prop, isCatchall = false) => { if (isLimitedKey(prop) && this.props[prop]) return true; if (isCatchall) return false; const error = new ValidationError(Messages.illegal(path), path); errors.push(error); return false; }); return errors; } /** * Validate given `obj`. * * @example * const schema = new Schema({ name: { required: true }}) * const errors = schema.validate({}) * assert(errors.length == 1) * assert(errors[0].message == 'name is required') * assert(errors[0].path == 'name') * * @param {Object} obj - the object to validate * @param {Object} [opts] - options, see [Schema](#schema-1) * @return {Array} */

validate(obj, opts = {}) { opts = Object.assign(this.opts, opts); const errors = [];
if (opts.typecast) { this.typecast(obj); }
if (opts.strict) { errors.push(...this.enforce(obj)); }
if (opts.strip !== false) { this.strip(obj); }
for (const [path, prop] of Object.entries(this.props)) { enumerate(path, obj, (key, value) => { const err = prop.validate(value, obj, key); if (err) errors.push(err); }); }
return errors; } /** * Assert that given `obj` is valid. * * @example * const schema = new Schema({ name: String }) * schema.assert({ name: 1 }) // Throws an error * * @param {Object} obj * @param {Object} [opts] */

assert(obj, opts) { const [err] = this.validate(obj, opts); if (err) throw err; }
message(name, message) { assign(name, message, this.messages); return this; }
validator(name, fn) { assign(name, fn, this.validators); return this; }
typecaster(name, fn) { assign(name, fn, this.typecasters); return this; } /** * Accepts a function that is called whenever new props are added. * * @param {Function} fn - the function to call * @return {Schema} * @private */

hook(fn) { this.hooks.push(fn); return this; } /** * Notify all subscribers that a property has been added. * * @param {String} path - the path of the property * @param {Property} prop - the new property * @return {Schema} * @private */

propagate(path, prop) { this.hooks.forEach(fn => fn(path, prop)); return this; }
}Schema.ValidationError = ValidationError;
export { Schema, dot$1 as dot, typecast$1 as typecast };