/* eslint no-console:0 */ import type { ValidateError, ValidateOption, RuleValuePackage, InternalRuleItem, SyncErrorType, RuleType, Value, Values, } from './interface'; const formatRegExp = /%[sdj%]/g; declare var ASYNC_VALIDATOR_NO_WARNING; export let warning: (type: string, errors: SyncErrorType[]) => void = () => {}; // don't print warning message when in production env or node runtime if ( typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production' && typeof window !== 'undefined' && typeof document !== 'undefined' ) { warning = (type, errors) => { if ( typeof console !== 'undefined' && console.warn && typeof ASYNC_VALIDATOR_NO_WARNING === 'undefined' ) { if (errors.every(e => typeof e === 'string')) { console.warn(type, errors); } } }; } export function convertFieldsError( errors: ValidateError[], ): Record { if (!errors || !errors.length) return null; const fields = {}; errors.forEach(error => { const field = error.field; fields[field] = fields[field] || []; fields[field].push(error); }); return fields; } export function format( template: ((...args: any[]) => string) | string, ...args: any[] ): string { let i = 0; const len = args.length; if (typeof template === 'function') { return template.apply(null, args); } if (typeof template === 'string') { let str = template.replace(formatRegExp, x => { if (x === '%%') { return '%'; } if (i >= len) { return x; } switch (x) { case '%s': return String(args[i++]); case '%d': return (Number(args[i++]) as unknown) as string; case '%j': try { return JSON.stringify(args[i++]); } catch (_) { return '[Circular]'; } break; default: return x; } }); return str; } return template; } function isNativeStringType(type: string) { return ( type === 'string' || type === 'url' || type === 'hex' || type === 'email' || type === 'date' || type === 'pattern' ); } export function isEmptyValue(value: Value, type?: string) { if (value === undefined || value === null) { return true; } if (type === 'array' && Array.isArray(value) && !value.length) { return true; } if (isNativeStringType(type) && typeof value === 'string' && !value) { return true; } return false; } export function isEmptyObject(obj: object) { return Object.keys(obj).length === 0; } function asyncParallelArray( arr: RuleValuePackage[], func: ValidateFunc, callback: (errors: ValidateError[]) => void, ) { const results: ValidateError[] = []; let total = 0; const arrLength = arr.length; function count(errors: ValidateError[]) { results.push(...(errors || [])); total++; if (total === arrLength) { callback(results); } } arr.forEach(a => { func(a, count); }); } function asyncSerialArray( arr: RuleValuePackage[], func: ValidateFunc, callback: (errors: ValidateError[]) => void, ) { let index = 0; const arrLength = arr.length; function next(errors: ValidateError[]) { if (errors && errors.length) { callback(errors); return; } const original = index; index = index + 1; if (original < arrLength) { func(arr[original], next); } else { callback([]); } } next([]); } function flattenObjArr(objArr: Record) { const ret: RuleValuePackage[] = []; Object.keys(objArr).forEach(k => { ret.push(...(objArr[k] || [])); }); return ret; } export class AsyncValidationError extends Error { errors: ValidateError[]; fields: Record; constructor( errors: ValidateError[], fields: Record, ) { super('Async Validation Error'); this.errors = errors; this.fields = fields; } } type ValidateFunc = ( data: RuleValuePackage, doIt: (errors: ValidateError[]) => void, ) => void; export function asyncMap( objArr: Record, option: ValidateOption, func: ValidateFunc, callback: (errors: ValidateError[]) => void, source: Values, ): Promise { if (option.first) { const pending = new Promise((resolve, reject) => { const next = (errors: ValidateError[]) => { callback(errors); return errors.length ? reject(new AsyncValidationError(errors, convertFieldsError(errors))) : resolve(source); }; const flattenArr = flattenObjArr(objArr); asyncSerialArray(flattenArr, func, next); }); pending.catch(e => e); return pending; } const firstFields = option.firstFields === true ? Object.keys(objArr) : option.firstFields || []; const objArrKeys = Object.keys(objArr); const objArrLength = objArrKeys.length; let total = 0; const results: ValidateError[] = []; const pending = new Promise((resolve, reject) => { const next = (errors: ValidateError[]) => { results.push.apply(results, errors); total++; if (total === objArrLength) { callback(results); return results.length ? reject( new AsyncValidationError(results, convertFieldsError(results)), ) : resolve(source); } }; if (!objArrKeys.length) { callback(results); resolve(source); } objArrKeys.forEach(key => { const arr = objArr[key]; if (firstFields.indexOf(key) !== -1) { asyncSerialArray(arr, func, next); } else { asyncParallelArray(arr, func, next); } }); }); pending.catch(e => e); return pending; } function isErrorObj( obj: ValidateError | string | (() => string), ): obj is ValidateError { return !!(obj && (obj as ValidateError).message !== undefined); } function getValue(value: Values, path: string[]) { let v = value; for (let i = 0; i < path.length; i++) { if (v == undefined) { return v; } v = v[path[i]]; } return v; } export function complementError(rule: InternalRuleItem, source: Values) { return (oe: ValidateError | (() => string) | string): ValidateError => { let fieldValue; if (rule.fullFields) { fieldValue = getValue(source, rule.fullFields); } else { fieldValue = source[(oe as any).field || rule.fullField]; } if (isErrorObj(oe)) { oe.field = oe.field || rule.fullField; oe.fieldValue = fieldValue; return oe; } return { message: typeof oe === 'function' ? oe() : oe, fieldValue, field: ((oe as unknown) as ValidateError).field || rule.fullField, }; }; } export function deepMerge(target: T, source: Partial): T { if (source) { for (const s in source) { if (source.hasOwnProperty(s)) { const value = source[s]; if (typeof value === 'object' && typeof target[s] === 'object') { target[s] = { ...target[s], ...value, }; } else { target[s] = value; } } } } return target; }