import React, { ReactNode } from 'react'; import { action, flow, isArrayLike, toJS } from 'mobx'; import isPromise from 'is-promise'; import isString from 'lodash/isString'; import noop from 'lodash/noop'; import ValidationResult from './ValidationResult'; import Record from '../data-set/Record'; import { Form } from '../interface'; import validationRules, { methodReturn, validationRule, ValidatorBaseProps, ValidatorProps } from './rules'; import valueMissing from './rules/valueMissing'; import { ValidationErrors, ValidationSelfErrors } from '../data-set/DataSet'; import { getGlobalConfig } from '../utils'; export type CustomValidator = ( value: any, name?: string, record?: Record | Form, ) => PromiseLike<boolean | string | undefined> | boolean | string | undefined; export type ValidationReport = { valid: boolean, validationResults: ValidationResult[]; validatorProps: ValidatorBaseProps; } export interface ValidationMessages { badInput?: ReactNode; patternMismatch?: ReactNode; rangeOverflow?: ReactNode; rangeUnderflow?: ReactNode; stepMismatch?: ReactNode; stepMismatchBetween?: ReactNode; tooLong?: ReactNode; tooShort?: ReactNode; typeMismatch?: ReactNode; valueMissing?: ReactNode; valueMissingNoLabel?: ReactNode; customError?: ReactNode; uniqueError?: ReactNode; unknown?: ReactNode; /** * 用于确认 valueMissing 展示信息 */ label?: ReactNode; } function* execute(rules: validationRule[], value: any[], props: ValidatorBaseProps, getProp: <T extends keyof ValidatorProps>(key: T) => ValidatorProps[T], validationResults: ValidationResult[]): any { const method = rules.shift(); if (method) { const results: methodReturn[] = []; const promises: PromiseLike<methodReturn>[] = []; value.forEach(item => { const result = method(item, props, getProp); if (isPromise(result)) { promises.push(result.then((re) => { results.push(re); return re; })); } else { results.push(result); } }); if (promises.length) { yield Promise.all<methodReturn>(promises); } results.forEach(result => { if (result instanceof ValidationResult) { validationResults.push(result); const index = value.indexOf(result.value); if (index !== -1) { value.splice(index, 1); } } }); if (value.length) { for (const result of execute(rules, value, props, getProp, validationResults)) { yield result; } } } } export default class Validator { static reportAll(errors: ValidationErrors[]) { const { length } = errors; if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined' && length > 0) { console.warn('validations:', toJS(errors)); } } static reportDataSet(errors: ValidationSelfErrors[]) { const { length } = errors; if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined' && length) { console.warn('validations:', toJS(errors)); } } static async report(results: ValidationResult[], props: ValidatorBaseProps) { if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined' && results.length) { const { name, dataSet, record } = props; const reportMessage: any[] = []; const promises: (PromiseLike<any> | any)[] = []; results.forEach(({ validationMessage, value }) => { promises.push( 'validation:', isString(validationMessage) ? validationMessage : getGlobalConfig('validationMessageReportFormatter', dataSet && dataSet.getField(name))(<span>{validationMessage}</span>), ); promises.push('\n[value]:', value); }); reportMessage.push(...await Promise.all(promises)); if (dataSet) { const { name: dsName, id } = dataSet; if (dsName || id) { reportMessage.push( ` [dataSet<${dsName || id}>]:`, dataSet, ); } else { reportMessage.push('\n[dataSet]:', dataSet); } } if (record) { if (dataSet) { reportMessage.push( ` [record<${dataSet.indexOf(record)}>]:`, record, ); } else { reportMessage.push(`\n[record]:`, record); } } if (name) { const field = (record && record.ownerFields.get(name)) || (dataSet && dataSet.getField(name)); if (field) { reportMessage.push( ` [field<${name}>]:`, field, ); } else { reportMessage.push('[field]:', name); } } console.warn(...reportMessage); } } @action static async checkValidity(value: unknown = null, props: ValidatorBaseProps = {}, getProp: <T extends keyof ValidatorProps>(key: T) => ValidatorProps[T] = noop): Promise<ValidationReport> { const validationResults: ValidationResult[] = []; const valueMiss: methodReturn = valueMissing(value, props, getProp); if (valueMiss !== true) { validationResults.push(valueMiss); } else { const multiple = getProp('multiple'); await flow(execute)( validationRules.slice(), multiple && isArrayLike(value) ? value.slice() : [value], props, getProp, validationResults, ); } return { valid: !validationResults.length, validationResults, validatorProps: props, }; } }