UNPKG

zent

Version:

一套前端设计语言和基于React的实现

226 lines (201 loc) 5.91 kB
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<T> { [ASYNC_VALIDATOR]: true; validator( input: T, ctx: ValidatorContext<T> ): null | Observable<IMaybeError<T>> | Promise<IMaybeError<T>>; $$id?: any; } export interface ISyncValidator<T> { (input: T, ctx: ValidatorContext<T>): IMaybeError<T>; $$id?: any; } export type IValidator<T> = IAsyncValidator<T> | ISyncValidator<T>; export type IValidators<T> = readonly IValidator<T>[]; /** * 判断一个校验函数是否是异步的,异步的校验函数必须使用 `createAsyncValidator` 创建 * @param validator 校验函数 */ export function isAsyncValidator<T>( validator: ISyncValidator<T> | IAsyncValidator<T> ): validator is IAsyncValidator<T> { if ((validator as ISyncValidator<T> & IAsyncValidator<T>)[ASYNC_VALIDATOR]) { return true; } return false; } /** * 创建一个异步校验函数 * @param validator 异步校验函数的实现 */ export function createAsyncValidator<T>( validator: ( value: T, context: ValidatorContext<T> ) => null | Observable<IMaybeError<T>> | Promise<IMaybeError<T>> ): IAsyncValidator<T> { return { [ASYNC_VALIDATOR]: true, validator, }; } /** * 校验结果 */ export interface IValidateResult<T> { /** * Validator 的名字,不是表单字段名 */ name: string; /** * 校验的错误信息 */ message?: string; /** * 校验时的期望值,一般用于自定义复杂的上下文相关的错误信息 */ expect?: T; /** * 校验时的实际值,一般用于自定义复杂的上下文相关的错误信息 */ actual?: T; [key: string]: any; } export type IMaybeError<T> = IValidateResult<T> | 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<any>): void; reject(error?: any): void; } export class ErrorSubscriber<T> implements NextObserver<IMaybeError<T>> { constructor(private readonly model: BasicModel<T>) {} next(error: IMaybeError<T>) { this.model.error = error; } } /** * 表单校验函数的上下文信息 */ export class ValidatorContext<T> { constructor(readonly model: BasicModel<T>) {} getSection(): BasicModel<T>['owner'] { return this.model.owner; } getSectionValue<T>(...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>(): T | null | undefined { return this.model.form?.getRawValue() as T | null | undefined; } } function runValidator<T>( validator: IAsyncValidator<T> | ISyncValidator<T>, { reject }: IValidation, value: T, ctx: ValidatorContext<T> ): Observable<IMaybeError<T>> { 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<T> { readonly ctx: ValidatorContext<T>; constructor(private readonly model: BasicModel<T>) { this.ctx = new ValidatorContext(model); } call(validation: IValidation): Observable<IMaybeError<T>> { 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<IMaybeError<T>>(resolve, null) ); } } /** * 执行 `model` 上的校验规则对 `model` 校验 * @param model 要校验的 model 对象 */ export function validate<T>(model: BasicModel<T>) { const executor = new ValidatorExecutor(model); return (validation: IValidation) => executor.call(validation); }