import { Observable, from, NextObserver, of, defer, EMPTY } from 'rxjs'; import { catchError, map, concatAll, filter, takeWhile } from 'rxjs/operators'; import { BasicModel, isFieldSetModel } from './models'; import { finalizeWithLast } from './finalize-with-last'; import isNil from '../../utils/isNil'; import { UnknownObject } from './utils'; export const ASYNC_VALIDATOR = Symbol('AsyncValidator'); export interface IAsyncValidator { [ASYNC_VALIDATOR]: true; validator( input: T, ctx: ValidatorContext ): null | Observable> | Promise>; $$id?: any; } export interface ISyncValidator { (input: T, ctx: ValidatorContext): IMaybeError; $$id?: any; } export type IValidator = IAsyncValidator | ISyncValidator; export type IValidators = readonly IValidator[]; /** * 判断一个校验函数是否是异步的,异步的校验函数必须使用 `createAsyncValidator` 创建 * @param validator 校验函数 */ export function isAsyncValidator( validator: ISyncValidator | IAsyncValidator ): validator is IAsyncValidator { if ((validator as ISyncValidator & IAsyncValidator)[ASYNC_VALIDATOR]) { return true; } return false; } /** * 创建一个异步校验函数 * @param validator 异步校验函数的实现 */ export function createAsyncValidator( validator: ( value: T, context: ValidatorContext ) => null | Observable> | Promise> ): IAsyncValidator { return { [ASYNC_VALIDATOR]: true, validator, }; } /** * 校验结果 */ export interface IValidateResult { /** * Validator 的名字,不是表单字段名 */ name: string; /** * 校验的错误信息 */ message?: string; /** * 校验时的期望值,一般用于自定义复杂的上下文相关的错误信息 */ expect?: T; /** * 校验时的实际值,一般用于自定义复杂的上下文相关的错误信息 */ actual?: T; [key: string]: any; } export type IMaybeError = IValidateResult | null | undefined; // prettier-ignore export enum ValidateOption { /** * 默认行为 */ Empty = 0b000000000, /** * 校验时包含异步校验 */ IncludeAsync = 0b000000010, /** * 校验时包含没有 `touch` 过的字段 */ IncludeUntouched = 0b000000100, /** * 递归校验下层的 `Field`,适用于直接从 `FieldSet` 和 `FieldArray` 触发的校验 */ IncludeChildrenRecursively = 0b000001000, /** * 不校验没有修改过的 `Field` */ ExcludePristine = 0b000010000, /** * 校验时不往上一级 `FieldSet` 或者 `FieldArray` 冒泡。 * 校验的冒泡会一直冒到表单最顶层。 */ StopPropagation = 0b000100000, Default = Empty, } export interface IValidation { option: ValidateOption; resolve(error?: IMaybeError): void; reject(error?: any): void; } export class ErrorSubscriber implements NextObserver> { constructor(private readonly model: BasicModel) {} next(error: IMaybeError) { this.model.error = error; } } /** * 表单校验函数的上下文信息 */ export class ValidatorContext { constructor(readonly model: BasicModel) {} getSection(): BasicModel['owner'] { return this.model.owner; } getSectionValue(...names: string[]): T | null { if (!this.model.owner || !isFieldSetModel(this.model.owner)) { return null; } if (names.length === 0) { return this.model.owner.getRawValue() as T; } const data: UnknownObject = {}; for (let i = 0; i < names.length; i += 1) { const name = names[i]; const model = this.model.owner.get(name); if (model) { data[name] = model.getRawValue(); } } return data as T; } getFormValue(): T | null | undefined { return this.model.form?.getRawValue() as T | null | undefined; } } function runValidator( validator: IAsyncValidator | ISyncValidator, { reject }: IValidation, value: T, ctx: ValidatorContext ): Observable> { try { if (isAsyncValidator(validator)) { const ret = validator.validator(value, ctx); if (ret === null) { return of(null); } return from(ret); } else { return of(validator(value, ctx)); } } catch (error) { reject(error); return EMPTY; } } class ValidatorExecutor { readonly ctx: ValidatorContext; constructor(private readonly model: BasicModel) { this.ctx = new ValidatorContext(model); } call(validation: IValidation): Observable> { const { option, reject, resolve } = validation; if (!this.model.touched() && !(option & ValidateOption.IncludeUntouched)) { resolve(); return of(null); } if (option & ValidateOption.ExcludePristine && this.model.pristine()) { resolve(); return of(null); } const value = this.model.getRawValue(); const skipAsync = (option & ValidateOption.IncludeAsync) === 0; return from(this.model.validators).pipe( filter(validator => (skipAsync ? !isAsyncValidator(validator) : true)), map(validator => defer(() => runValidator(validator, validation, value, this.ctx)) ), concatAll(), takeWhile(isNil, true), catchError(error => { reject(error); return EMPTY; }), finalizeWithLast>(resolve, null) ); } } /** * 执行 `model` 上的校验规则对 `model` 校验 * @param model 要校验的 model 对象 */ export function validate(model: BasicModel) { const executor = new ValidatorExecutor(model); return (validation: IValidation) => executor.call(validation); }