zent
Version:
一套前端设计语言和基于React的实现
226 lines (201 loc) • 5.91 kB
text/typescript
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);
}