PHP Classes

File: src/js/Formal.js

Recommend this page to a friend!
  Classes of Nikos M.  >  Formal PHP Validation Library  >  src/js/Formal.js  >  Download  
File: src/js/Formal.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Formal PHP Validation Library
Validate a set values with support to type casting
Author: By
Last change: v.1.2.0

* correctly handle (wildcard) missing keys in typecast/validate
* correctly handle some edge cases when input data is not array or object
* update tests
Date: 3 months ago
Size: 46,601 bytes
 

Contents

Class file image Download
/** * Formal * validate nested (form) data with built-in and custom rules for PHP, JavaScript, Python * * @version 1.2.0 * https://github.com/foo123/Formal * **/ !function(root, name, factory) { "use strict"; if (('object' === typeof module) && module.exports) /* CommonJS */ (module.$deps = module.$deps||{}) && (module.exports = module.$deps[name] = factory.call(root)); else if (('function' === typeof define) && define.amd && ('function' === typeof require) && ('function' === typeof require.specified) && require.specified(name) /*&& !require.defined(name)*/) /* AMD */ define(name, ['module'], function(module) {factory.moduleUri = module.uri; return factory.call(root);}); else if (!(name in root)) /* Browser/WebWorker/.. */ (root[name] = factory.call(root)||1) && ('function' === typeof(define)) && define.amd && define(function() {return root[name];}); }( /* current root */ 'undefined' !== typeof self ? self : this, /* module name */ "Formal", /* module factory */ function ModuleFactory__Formal(undef) { "use strict"; var HAS = Object.prototype.hasOwnProperty, toString = Object.prototype.toString, ESC_RE = /[.*+?^${}()|[\]\\]/g, EMAIL_RE = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, URL_RE = new RegExp('^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$','i'), isNode = ("undefined" !== typeof global) && ("[object global]" === toString.call(global)), isBrowser = ("undefined" !== typeof window) && ("[object Window]" === toString.call(window)) ; function is_numeric(x) { return !isNaN(+x); } function is_string(x) { return ('string' === typeof(x)) || ('[object String]' === toString.call(x)); } function is_array(x) { return ('[object Array]' === toString.call(x)); } function is_object(x) { return ('[object Object]' === toString.call(x)) && ('function' === typeof x.constructor) && ('Object' === x.constructor.name); } function is_array_or_object(x) { return ('[object Array]' === toString.call(x)) || (('[object Object]' === toString.call(x)) && ('function' === typeof x.constructor) && ('Object' === x.constructor.name)); } async function is_file(x) { if (isNode) { return await new Promise(function(resolve) { require('fs').lstat(String(x), function(err, stats) { resolve(err || !stats ? false : stats.isFile()); }); }); } else if (isBrowser) { return ('File' in window) && (x instanceof File); } return false; } async function filesize(x) { if (isNode) { return await new Promise(function(resolve) { require('fs').lstat(String(x), function(err, stats) { resolve(err || !stats ? false : stats.size); }); }); } else if (isBrowser) { return ('File' in window) && (x instanceof File) ? x.size : false; } return false; } function is_callable(x) { return 'function' === typeof(x); } function method_exists(o, m) { return is_callable(o[m]); } function array(a) { return is_array(a) ? a : [a]; } function empty(x) { return (null == x) || (0 === x) || (false === x) || ('' === x) || (is_array(x) && !x.length) || (is_object(x) && !Object.keys(x).length); } function is_null(x) { return null == x; } function esc_re(s) { return s.replace(ESC_RE, '\\$&'); } function by_length_desc(a, b) { return b.length-a.length; } function get_alternate_pattern(alts) { alts.sort(by_length_desc); return alts.map(esc_re).join('|'); } function clone(o) { if (is_array(o)) { return o.map(clone); } else if (is_object(o)) { return Object.keys(o).reduce(function(oo, k) { oo[k] = clone(o[k]); return oo; }, {}); } else { return o; } } class FormalException extends Error { constructor(message) { super(message); this.name = "FormalException"; } } class FormalField { field = null; constructor(field) { this.field = field; } } class FormalDateTime { format = ''; pattern = null; constructor(format, locale = null) { if (!locale) locale = { 'day_short' : ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], 'day' : ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], 'month_short' : ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 'month' : ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], 'meridian' : {'am' : 'am', 'pm' : 'pm', 'AM' : 'AM', 'PM' : 'PM'}, 'timezone_short' : ['UTC'], 'timezone' : ['UTC'], 'ordinal' : {'ord' : {'1' : 'st', '2' : 'nd', '3' : 'rd'}, 'nth' : 'th'}, }; // (php) date formats // http://php.net/manual/en/function.date.php var D = { // Day -- // Day of month w/leading 0; 01..31 'd': '(31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01)' // Shorthand day name; Mon...Sun ,'D': '(' + get_alternate_pattern(locale['day_short']) + ')' // Day of month; 1..31 ,'j': '(31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1)' // Full day name; Monday...Sunday ,'l': '(' + get_alternate_pattern(locale['day']) + ')' // ISO-8601 day of week; 1[Mon]..7[Sun] ,'N': '([1-7])' // Ordinal suffix for day of month; st, nd, rd, th ,'S': '' // added below // Day of week; 0[Sun]..6[Sat] ,'w': '([0-6])' // Day of year; 0..365 ,'z': '([1-3]?[0-9]{1,2})' // Week -- // ISO-8601 week number ,'W': '([0-5]?[0-9])' // Month -- // Full month name; January...December ,'F': '(' + get_alternate_pattern(locale['month']) + ')' // Month w/leading 0; 01...12 ,'m': '(12|11|10|09|08|07|06|05|04|03|02|01)' // Shorthand month name; Jan...Dec ,'M': '(' + get_alternate_pattern(locale['month_short']) + ')' // Month; 1...12 ,'n': '(12|11|10|9|8|7|6|5|4|3|2|1)' // Days in month; 28...31 ,'t': '(31|30|29|28)' // Year -- // Is leap year?; 0 or 1 ,'L': '([01])' // ISO-8601 year ,'o': '(\\d{2,4})' // Full year; e.g. 1980...2010 ,'Y': '([12][0-9]{3})' // Last two digits of year; 00...99 ,'y': '([0-9]{2})' // Time -- // am or pm ,'a': '(' + get_alternate_pattern([ locale['meridian']['am'], locale['meridian']['pm'] ]) + ')' // AM or PM ,'A': '(' + get_alternate_pattern([ locale['meridian']['AM'], locale['meridian']['PM'] ]) + ')' // Swatch Internet time; 000..999 ,'B': '([0-9]{3})' // 12-Hours; 1..12 ,'g': '(12|11|10|9|8|7|6|5|4|3|2|1)' // 24-Hours; 0..23 ,'G': '(23|22|21|20|19|18|17|16|15|14|13|12|11|10|9|8|7|6|5|4|3|2|1|0)' // 12-Hours w/leading 0; 01..12 ,'h': '(12|11|10|09|08|07|06|05|04|03|02|01)' // 24-Hours w/leading 0; 00..23 ,'H': '(23|22|21|20|19|18|17|16|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00)' // Minutes w/leading 0; 00..59 ,'i': '([0-5][0-9])' // Seconds w/leading 0; 00..59 ,'s': '([0-5][0-9])' // Microseconds; 000000-999000 ,'u': '([0-9]{6})' // Timezone -- // Timezone identifier; e.g. Atlantic/Azores, ... ,'e': '(' + get_alternate_pattern(locale['timezone']) + ')' // DST observed?; 0 or 1 ,'I': '([01])' // Difference to GMT in hour format; e.g. +0200 ,'O': '([+-][0-9]{4})' // Difference to GMT w/colon; e.g. +02:00 ,'P': '([+-][0-9]{2}:[0-9]{2})' // Timezone abbreviation; e.g. EST, MDT, ... ,'T': '(' + get_alternate_pattern(locale['timezone_short']) + ')' // Timezone offset in seconds (-43200...50400) ,'Z': '(-?[0-9]{5})' // Full Date/Time -- // Seconds since UNIX epoch ,'U': '([0-9]{1,8})' // ISO-8601 date. Y-m-d\\TH:i:sP ,'c': '' // added below // RFC 2822 D, d M Y H:i:s O ,'r': '' // added below }; // Ordinal suffix for day of month; st, nd, rd, th var lords = Object.values(locale['ordinal']['ord']); lords.push(locale['ordinal']['nth']); D['S'] = '(' + get_alternate_pattern(lords) + ')'; // ISO-8601 date. Y-m-d\\TH:i:sP D['c'] = D['Y']+'-'+D['m']+'-'+D['d']+'\\\\'+D['T']+D['H']+':'+D['i']+':'+D['s']+D['P']; // RFC 2822 D, d M Y H:i:s O D['r'] = D['D']+',\\s'+D['d']+'\\s'+D['M']+'\\s'+D['Y']+'\\s'+D['H']+':'+D['i']+':'+D['s']+'\\s'+D['O']; var re = '', l = format.length, i, f; for (i=0; i<l; ++i) { f = format.charAt(i); re += HAS.call(D, f) ? D[ f ] : esc_re(f); } this.format = format; this.pattern = new RegExp('^' + re + '$', ''); } getFormat() { return this.format; } getPattern() { return this.pattern; } toString() { return this.pattern.toString(); } } class FormalType { func = null; inp = null; constructor(type, args = null) { if (type instanceof FormalType) { this.func = type.func; this.inp = type.inp; } else { var method = is_string(type) ? 't_' + String(type).trim().toLowerCase() : null; this.func = method && method_exists(this, method) ? method : (is_callable(type) ? type : null); this.inp = args; } } async exec(v, k = null, m = null) { if (is_string(this.func)) { v = await this[this.func](v, k, m); } else if (is_callable(this.func)) { v = await this.func(v, this.inp, k, m); } return v; } async t_composite(v, k, m) { var types = array(this.inp), l = types.length, i = 0; for (i=0; i<l; ++i) { v = await types[i].exec(v, k, m); } return v; } /*async t_fields(v, k, m) { if (!is_object(v) && !is_array(v)) return v; var SEPARATOR = m.option('SEPARATOR'), field, type; for (field in this.inp) { if (!HAS.call(this.inp, field)) continue; type = this.inp[field]; v[field] = await type.exec(HAS.call(v, field) ? v[field] : undef, empty(k) ? field : k+SEPARATOR+field, m); } return v; } t_default(v, k, m) { var defaultValue = this.inp; if (is_null(v) || (is_string(v) && !v.trim().length)) { v = defaultValue; } return v; }*/ t_bool(v, k, m) { // handle string representation of booleans as well if (is_string(v) && v.length) { var vs = v.toLowerCase(); return 'true' === vs || 'on' === vs || '1' === vs; } return !!v; } t_int(v, k, m) { return parseInt(v); } t_float(v, k, m) { return parseFloat(v); } t_str(v, k, m) { return String(v); } t_min(v, k, m) { var min = this.inp; return v < min ? min : v; } t_max(v, k, m) { var max = this.inp; return v > max ? max : v; } t_clamp(v, k, m) { var min = this.inp[0], max = this.inp[1]; return v < min ? min : (v > max ? max : v); } t_trim(v, k, m) { return String(v).trim(); } t_lower(v, k, m) { return String(v).toLowerCase(); } t_upper(v, k, m) { return String(v).toUpperCase(); } } class FormalValidator { func = null; inp = null; msg = null; constructor(validator, args = null, msg = null) { if (validator instanceof FormalValidator) { this.func = validator.func; this.inp = validator.inp; this.msg = empty(msg) ? validator.msg : msg; } else { var method = is_string(validator) ? 'v_' + String(validator).trim().toLowerCase() : null; this.func = method && method_exists(this, method) ? method : (is_callable(validator) ? validator : null); this.inp = args; this.msg = msg; } } _and_(validator) { return new FormalValidator('and', [this, validator]); } _or_(validator) { return new FormalValidator('or', [this, validator]); } _not_(msg = null) { return new FormalValidator('not', this, msg); } async exec(v, k = null, m = null, missingValue = false) { var valid = true; if (is_string(this.func)) { valid = !!(await this[this.func](v, k, m, missingValue)); } else if (is_callable(this.func)) { valid = !!(await this.func(v, this.inp, k, m, missingValue, this.msg)); } return valid; } async v_and(v, k, m, missingValue) { var valid = (await this.inp[0].exec(v, k, m, missingValue)) && (await this.inp[1].exec(v, k, m, missingValue)); return valid; } async v_or(v, k, m, missingValue) { var msg1 = null, msg2 = null, valid1 = false, valid2 = false, valid; try { valid1 = await this.inp[0].exec(v, k, m, missingValue); } catch (e) { if (e instanceof FormalException) { valid1 = false; msg1 = e.message; } else { throw e; } } if (!valid1) { try { valid2 = await this.inp[1].exec(v, k, m, missingValue); } catch (e) { if (e instanceof FormalException) { valid2 = false; msg2 = e.message; } else { throw e; } } } valid = valid1 || valid2; if (!valid && (!empty(msg1) || !empty(msg2))) throw new FormalException(empty(msg1) ? msg2 : msg1); return valid; } async v_not(v, k, m, missingValue) { var valid; try { valid = !(await this.inp.exec(v, k, m, missingValue)); } catch (e) { if (e instanceof FormalException) { valid = true; } else { throw e; } } if (!valid && !empty(this.msg)) throw new FormalException(this.msg.replace('{key}', k).replace('{args}', '')); return valid; } async v_optional(v, k, m, missingValue) { var valid = true; if (!missingValue) { valid = await this.inp.exec(v, k, m, false); } return valid; } v_required(v, k, m, missingValue) { var valid = !missingValue && !is_null(v); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', '') : "\""+k+"\" is required!"); return valid; } /*async v_fields(v, k, m, missingValue) { if (!is_object(v) && !is_array(v)) return false; var SEPARATOR = m.option('SEPARATOR'), field, validator; for (field in this.inp) { if (!HAS.call(this.inp, field)) continue; validator = this.inp[field]; if (!HAS.call(v, field)) { if (!(await validator.exec(undef, empty(k) ? field : k+SEPARATOR+field, m, true))) return false; } else { if (!(await validator.exec(v[field], empty(k) ? field : k+SEPARATOR+field, m, missingValue))) return false; } } return true; }*/ v_numeric(v, k, m, missingValue) { var valid = is_numeric(v); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', '') : "\""+k+"\" must be numeric value!"); return valid; } v_object(v, k, m, missingValue) { var valid = is_object(v); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', '') : "\""+k+"\" must be an object!"); return valid; } v_array(v, k, m, missingValue) { var valid = is_array(v); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', '') : "\""+k+"\" must be an array!"); return valid; } async v_file(v, k, m, missingValue) { var valid = await is_file(v); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', '') : "\""+k+"\" must be a file!"); return valid; } v_empty(v, k, m, missingValue) { var valid = missingValue || is_null(v) || (is_array(v) ? !v.length : (is_object(v) ? !Object.keys(v).length : !String(v).trim().length)); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', '') : "\""+k+"\" must be empty!"); return valid; } v_maxitems(v, k, m, missingValue) { var valid = v.length <= this.inp; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', this.inp) : "\""+k+"\" must have at most "+this.inp+" items!"); return valid; } v_minitems(v, k, m, missingValue) { var valid = v.length >= this.inp; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', this.inp) : "\""+k+"\" must have at least "+this.inp+" items!"); return valid; } v_maxchars(v, k, m, missingValue) { var valid = v.length <= this.inp; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', this.inp) : "\""+k+"\" must have at most "+this.inp+" characters!"); return valid; } v_minchars(v, k, m, missingValue) { var valid = v.length >= this.inp; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', this.inp) : "\""+k+"\" must have at least "+this.inp+" characters!"); return valid; } async v_maxsize(v, k, m, missingValue) { var fs = false, valid = false; fs = await filesize(String(v)); valid = false === fs ? false : fs <= this.inp; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', this.inp) : "\""+k+"\" must have at most "+this.inp+" bytes!"); return valid; } async v_minsize(v, k, m, missingValue) { var fs = false, valid = false; fs = await filesize(String(v)); valid = false === fs ? false : fs >= this.inp; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', this.inp) : "\""+k+"\" must have at least "+this.inp+" bytes!"); return valid; } v_eq(v, k, m, missingValue) { var val = this.inp, valm = val, valid; if (val instanceof FormalField) { valm = !empty(this.msg) ? val.field : '"' + val.field + '"'; val = m.get(val.field); } valid = val === v; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', valm) : "\""+k+"\" must be equal to "+valm+"!"); return valid; } v_neq(v, k, m, missingValue) { var val = this.inp, valm = val, valid; if (val instanceof FormalField) { valm = !empty(this.msg) ? val.field : '"' + val.field + '"'; val = m.get(val.field); } valid = val != v; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', valm) : "\""+k+"\" must not be equal to "+valm+"!"); return valid; } v_gt(v, k, m, missingValue) { var val = this.inp, valm = val, valid; if (val instanceof FormalField) { valm = !empty(this.msg) ? val.field : '"' + val.field + '"'; val = m.get(val.field); } valid = v > val; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', valm) : "\""+k+"\" must be greater than "+valm+"!"); return valid; } v_gte(v, k, m, missingValue) { var val = this.inp, valm = val, valid; if (val instanceof FormalField) { valm = !empty(this.msg) ? val.field : '"' + val.field + '"'; val = m.get(val.field); } valid = v >= val; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', valm) : "\""+k+"\" must be greater than or equal to "+valm+"!"); return valid; } v_lt(v, k, m, missingValue) { var val = this.inp, valm = val, valid if (val instanceof FormalField) { valm = !empty(this.msg) ? val.field : '"' + val.field + '"'; val = m.get(val.field); } valid = v < val; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', valm) : "\""+k+"\" must be less than "+valm+"!"); return valid; } v_lte(v, k, m, missingValue) { var val = this.inp, valm = val, valid; if (val instanceof FormalField) { valm = !empty(this.msg) ? val.field : '"' + val.field + '"'; val = m.get(val.field); } valid = v <= val; if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', valm) : "\""+k+"\" must be less than or equal to "+valm+"}!"); return valid; } v_between(v, k, m, missingValue) { var min = this.inp[0], max = this.inp[1], minm = min, maxm = max, valid; if (min instanceof FormalField) { minm = !empty(this.msg) ? min.field : '"' + min.field + '"'; min = m.get(min.field); } if (max instanceof FormalField) { maxm = !empty(this.msg) ? max.field : '"' + max.field + '"'; max = m.get(max.field); } valid = (min <= v) && (v <= max); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', [minm, maxm].join(',')) : "\""+k+"\" must be between "+minm+" and "+maxm+"!"); return valid; } v_in(v, k, m, missingValue) { var val = this.inp, valm, valid; if (val instanceof FormalField) { valm = !empty(this.msg) ? val.field : '"' + val.field + '"'; val = m.get(val.field); } else { valm = !empty(this.msg) ? array(val).join(',') : '[' + array(val).join(',') + ']'; } valid = -1 !== array(val).indexOf(v); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', valm) : "\""+k+"\" must be one of "+valm+"!"); return valid; } v_not_in(v, k, m, missingValue) { var val = this.inp, valm, valid; if (val instanceof FormalField) { valm = !empty(this.msg) ? val.field : '"' + val.field + '"'; val = m.get(val.field); } else { valm = !empty(this.msg) ? array(val).join(',') : '[' + array(val).join(',') + ']'; } valid = -1 === array(val).indexOf(v); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', valm) : "\""+k+"\" must not be one of "+valm+"!"); return valid; } v_match(v, k, m, missingValue) { var re = this.inp instanceof RegExp ? this.inp : (this.inp instanceof FormalDateTime ? this.inp.getPattern() : new RegExp(String(this.inp), '')), valid = re.test(String(v)); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', this.inp instanceof FormalDateTime ? this.inp.getFormat() : this.inp) : "\""+k+"\" must match " + (this.inp instanceof FormalDateTime ? '"' + this.inp.getFormat() + '"' : 'the') + " pattern!"); return valid; } v_email(v, k, m, missingValue) { var valid = EMAIL_RE.test(String(v)); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', '') : "\""+k+"\" must be valid email pattern!"); return valid; } v_url(v, k, m, missingValue) { var valid = URL_RE.test(String(v)); if (!valid) throw new FormalException(!empty(this.msg) ? this.msg.replace('{key}', k).replace('{args}', '') : "\""+k+"\" must be valid url pattern!"); return valid; } } class FormalError { key = null; msg = ''; constructor(msg = '', key = []) { this.msg = String(msg); this.key = key; } getMsg() { return this.msg; } getKey() { return this.key; } toString() { return this.msg; } } class Formal { static VERSION = "1.2.0"; // export these static Exception = FormalException; static Field = FormalField; static DateTime = FormalDateTime; static Type = FormalType; static Validator = FormalValidator; static Error = FormalError; static field(field) { return new FormalField(field); } static datetime(format, locale = null) { return new FormalDateTime(format, locale); } static typecast(type, args = null) { return new FormalType(type, args); } static validate(validator, args = null, msg = null) { return new FormalValidator(validator, args, msg); } opts = null; err = null; data = null; constructor() { this.opts = {}; this.err = []; this.data = null; this .option('WILDCARD', '*') .option('SEPARATOR', '.') .option('break_on_first_error', false) .option('invalid_value_msg', 'Invalid Value in "{key}"!') .option('missing_value_msg', 'Missing Value in "{key}"!') .option('defaults', {}) .option('typecasters', {}) .option('validators', {}) ; } option(key, val = null) { var nargs = arguments.length; if (1 == nargs) { return HAS.call(this.opts, key) ? this.opts[key] : undef; } else if (1 < nargs) { this.opts[key] = val; } return this; } async process(data) { var WILDCARD = this.option('WILDCARD'), SEPARATOR = this.option('SEPARATOR'); this.data = null; this.err = []; data = clone(data); data = this.doMergeDefaults(data, this.option('defaults'), WILDCARD, SEPARATOR); data = await this.doTypecast(data, this.option('typecasters'), [], [], WILDCARD, SEPARATOR); this.data = data; await this.doValidate(data, this.option('validators'), [], [], WILDCARD, SEPARATOR); this.data = null; return data; } getErrors() { return this.err; } get(field, _default = null, data = null) { if (null == data) data = this.data; var WILDCARD = this.option('WILDCARD'), SEPARATOR = this.option('SEPARATOR'), is_array_result = false, is_result_set = false, result = null, stack, to_get, o, key, p, i, l, k; if ((is_string(field) || is_numeric(field)) && (is_object(data) || is_array(data))) { stack = [[data, String(field)]]; while (stack.length) { to_get = stack.shift(); o = to_get[0]; key = to_get[1]; p = key.split(SEPARATOR); i = 0; l = p.length; while (i < l) { k = p[i++]; if (i < l) { if (is_object(o)) { if (WILDCARD === k) { is_array_result = true; k = p.slice(i).join(SEPARATOR); Object.keys(o).forEach(function(key) { stack.push([o, key+SEPARATOR+k]); }); break; } else if (HAS.call(o, k)) { o = o[k]; } else { break; } } else if (is_array(o)) { if (WILDCARD === k) { is_array_result = true; k = p.slice(i).join(SEPARATOR); Object.keys(o).forEach(function(key) { stack.push([o, key+SEPARATOR+k]); }); break; } else if (HAS.call(o, k)) { o = o[k]; } else { break; } } else { break; } } else { if (is_object(o)) { if (WILDCARD === k) { is_array_result = true; if (!is_result_set) result = []; result.push.apply(result, Object.values(o)); is_result_set = true; } else if (HAS.call(o, k)) { if (is_array_result) { if (!is_result_set) result = []; result.push(o[k]); } else { result = o[k]; } is_result_set = true; } else { if (is_array_result) { if (!is_result_set) result = []; result.push(_default); } else { result = _default; } is_result_set = true; } } else if (is_array(o)) { if (WILDCARD === k) { is_array_result = true; if (!is_result_set) result = []; result.push.apply(result, o); is_result_set = true; } else if (HAS.call(o, k)) { if (is_array_result) { if (!is_result_set) result = []; result.push(o[k]); } else { result = o[k]; } is_result_set = true; } else { if (is_array_result) { if (!is_result_set) result = []; result.push(_default); } else { result = _default; } is_result_set = true; } } } } } return is_result_set ? result : _default; } return _default; } doMergeKeys(keys, def) { var n = keys.length, defaults = def, i, o, k; for (i=n-1; i>=0; --i) { o = {}; k = keys[i]; if (is_array(k)) { k.forEach(function(kk) { o[kk] = clone(defaults); }); } else { o[k] = defaults; } defaults = o; } return defaults; } doMergeDefaults(data, defaults, WILDCARD = '*', SEPARATOR = '.') { if (is_array_or_object(data) && is_array_or_object(defaults)) { var keys, key, def, k, kk, n, o, doMerge, i, ok; for (key in defaults) { if (!HAS.call(defaults, key)) continue; def = defaults[key]; kk = key.split(SEPARATOR); n = kk.length; if (1 < n) { o = data; keys = []; doMerge = true; for (i=0; i<n; ++i) { k = kk[i]; if (WILDCARD === k) { ok = is_array_or_object(o) ? Object.keys(o) : []; if (!ok.length) { doMerge = false; break; } keys.push(ok); o = o[ok[0]]; } else if (is_array_or_object(o) && HAS.call(o, k)) { keys.push(k); o = o[k]; } else if (i === n-1) { keys.push(k); } else { doMerge = false; break; } } if (doMerge) { data = this.doMergeDefaults(data, this.doMergeKeys(keys, def), WILDCARD, SEPARATOR); } } else { if (HAS.call(data, key)) { if (is_array_or_object(data[key]) && is_array_or_object(def)) { data[key] = this.doMergeDefaults(data[key], def, WILDCARD, SEPARATOR); } else if (is_null(data[key]) || (is_string(data[key]) && !data[key].trim().length)) { data[key] = clone(def); } } else { data[key] = clone(def); } } } } else if (is_null(data[key]) || (is_string(data) && !data.trim().length)) { data = clone(defaults); } return data; } async doTypecast(data, typecaster, key = [], root = [], WILDCARD = '*', SEPARATOR = '.') { if (typecaster instanceof FormalType) { var n = key.length, i = 0, k, rk, ok, j, m, KEY; if (i < n) { k = key[i++]; if ('' === k) { return data; } else if (WILDCARD === k) { if (i < n) { ok = is_array_or_object(data) ? Object.keys(data) : []; if (ok.length) { rk = key.slice(i); root = root.concat(key.slice(0, i-1)); for (j=0,m=ok.length; j<m; ++j) { data[ok[j]] = await this.doTypecast(data[ok[j]], typecaster, rk, root.concat(array(ok[j])), WILDCARD, SEPARATOR); } } } else { ok = is_array_or_object(data) ? Object.keys(data) : []; if (ok.length) { root = root.concat(key.slice(0, i-1)); for (j=0,m=ok.length; j<m; ++j) { data = await this.doTypecast(data, typecaster, array(ok[j]), root, WILDCARD, SEPARATOR); } } } return data; } else if (is_array_or_object(data) && HAS.call(data, k)) { rk = key.slice(i); root = root.concat(key.slice(0, i)); data[k] = await this.doTypecast(data[k], typecaster, rk, root, WILDCARD, SEPARATOR); } else { return data; } } else { KEY = root.concat(key).join(SEPARATOR); data = await typecaster.exec(data, KEY, this); } } else if (is_array_or_object(typecaster)) { for (var k in typecaster) { if (!HAS.call(typecaster, k)) continue; data = await this.doTypecast(data, typecaster[k], empty(key) ? k.split(SEPARATOR) : key.concat(k.split(SEPARATOR)), root, WILDCARD, SEPARATOR); } } return data; } async doValidate(data, validator, key = [], root = [], WILDCARD = '*', SEPARATOR = '.') { if (this.option('break_on_first_error') && this.err.length) return; if (validator instanceof FormalValidator) { var n = key.length, i = 0, k, rk, ok, j, m, KEY, KEY_, valid, err; while (i < n) { k = key[i++]; if ('' === k) { continue; } else if (WILDCARD === k) { if (i < n) { ok = is_array_or_object(data) ? Object.keys(data) : []; if (!ok.length) { KEY_ = root.concat(key); KEY = KEY_.join(SEPARATOR); err = null; try { valid = await validator.exec(null, KEY, this, true); } catch (e) { if (e instanceof FormalException) { valid = false; err = e.message; } else { throw e; } } if (!valid) { this.err.push(new FormalError(empty(err) ? this.option('missing_value_msg').replace('{key}', KEY).replace('{args}', '') : err, KEY_)); } return; } else { rk = key.slice(i); root = root.concat(key.slice(0, i-1)); for (j=0,m=ok.length; j<m; ++j) { await this.doValidate(data[ok[j]], validator, rk, root.concat(array(ok[j])), WILDCARD, SEPARATOR); } } } else { ok = is_array_or_object(data) ? Object.keys(data) : []; if (!ok.length) { KEY_ = root.concat(key); KEY = KEY_.join(SEPARATOR); err = null; try { valid = await validator.exec(null, KEY, this, true); } catch (e) { if (e instanceof FormalException) { valid = false; err = e.message; } else { throw e; } } if (!valid) { this.err.push(new FormalError(empty(err) ? this.option('missing_value_msg').replace('{key}', KEY).replace('{args}', '') : err, KEY_)); } } else { root = root.concat(key.slice(0, i-1)); for (j=0,m=ok.length; j<m; ++j) { await this.doValidate(data, validator, array(ok[j]), root, WILDCARD, SEPARATOR); } } } return; } else if (is_array_or_object(data) && HAS.call(data, k)) { data = data[k]; } else { KEY_ = root.concat(key); KEY = KEY_.join(SEPARATOR); err = null; try { valid = await validator.exec(null, KEY, this, true); } catch (e) { if (e instanceof FormalException) { valid = false; err = e.message; } else { throw e; } } if (!valid) { this.err.push(new FormalError(empty(err) ? this.option('missing_value_msg').replace('{key}', KEY).replace('{args}', '') : err, KEY_)); } return; } } KEY_ = root.concat(key); KEY = KEY_.join(SEPARATOR); err = null; try { valid = await validator.exec(data, KEY, this, false); } catch (e) { if (e instanceof FormalException) { valid = false; err = e.message; } else { throw e; } } if (!valid) { this.err.push(new FormalError(empty(err) ? this.option('invalid_value_msg').replace('{key}', KEY).replace('{args}', '') : err, KEY_)); } } else if (is_array_or_object(validator)) { for (var k in validator) { if (!HAS.call(validator, k)) continue; await this.doValidate(data, validator[k], empty(key) ? k.split(SEPARATOR) : key.concat(k.split(SEPARATOR)), root, WILDCARD, SEPARATOR); } } } } // export it return Formal; });