Skip to content

Commit

Permalink
feat: 增加矩阵数据格式校验
Browse files Browse the repository at this point in the history
  • Loading branch information
D-xuanmo committed May 13, 2024
1 parent e476809 commit 603556a
Show file tree
Hide file tree
Showing 6 changed files with 291 additions and 50 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,5 @@ dist
*.njsproj
*.sln
*.sw?
test.html

test/__snapshots__
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,38 @@ validator
validator() {
return false
}
},
// 以下为矩阵校验示例
{
// 需要指定为矩阵校验
matrix: true,
// 矩阵对应的 id,整个校验数据唯一
matrixId: 'matrixId',
value: {
columns: [
{
dataKey: 'column1',
label: '矩阵列 1',
rules: 'required|min_length:9'
},
{
dataKey: 'column2',
label: '矩阵列 2',
rules: 'confirmed:@column1'
}
],
data: [
{
rowId: 'a',
column2: '222value'
},
{
rowId: 'b',
column1: 22,
column2: 'xxx'
}
]
}
}
])
.then(() => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xuanmo/validator",
"version": "0.0.17",
"version": "0.0.18",
"description": "用最少的代码,解决繁琐的事情",
"private": false,
"main": "dist/validator.cjs.js",
Expand Down
89 changes: 85 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export type ValidateReturnType = Promise<boolean | ValidateErrorType>
*/
export type ValidateErrorType = string

export type ValidateDataModelItem = {
export type ValidateDataModelBaseItem = {
/**
* 数据对应的键值
*/
Expand All @@ -41,18 +41,99 @@ export type ValidateDataModelItem = {
*/
label?: string

/**
* 数据每行对应的 id
*/
rowId: string

/**
* 错误信息,优先级最高
*/
message?: string
} & OmitObjectProperties<ValidatorModelType, 'message'>

export type MatrixColumnsType = OmitObjectProperties<ValidateDataModelBaseItem, 'value'>[]

export type ValidateDataModelMatrix = OmitObjectProperties<ValidateDataModelBaseItem, 'value'> & {
/**
* 数据是否为矩阵格式
*/
matrix: true

/**
* 矩阵 id
*/
matrixId: string

/**
* 矩阵数据
*/
value: {
/**
* 矩阵列
* @example 数据格式
* [
* {
* dataKey: 'column1',
* label: '矩阵列 1'
* },
* {
* dataKey: 'column2',
* label: '矩阵列 2'
* }
* ]
*/
columns: MatrixColumnsType

/**
* 矩阵主体数据
* @example 数据格式
* [
* {
* rowId: 'a',
* column1: 1,
* column2: 'value'
* },
* {
* rowId: 'b',
* column1: 22,
* column2: 'xxx'
* }
* ]
*/
data: Array<{
/**
* 数据每行对应的 id
*/
rowId: string

/**
* 当前矩阵
*/
[key: string]: any
}>
}
}

export type ValidateDataModelItem = ValidateDataModelBaseItem | ValidateDataModelMatrix

/**
* 校验数据模型
* @example 数据示例
* [
* {
* value: 'xxx',
* label: '名称',
* dataKey: 'field1'
* },
* {
* value: 'xxx',
* label: '名称',
* dataKey: 'field2'
* }
* ]
*/
export type ValidateDataModel =
| Record<string, OmitObjectProperties<ValidateDataModelItem, 'dataKey'> & { dataKey?: string }>
| Array<ValidateDataModelItem>
export type ValidateDataModel = Array<ValidateDataModelItem>

/**
* 所有内置的规则
Expand Down
125 changes: 81 additions & 44 deletions src/validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ValidateContextType,
ValidateDataModel,
ValidateDataModelItem,
ValidateDataModelMatrix,
ValidateErrorType,
ValidateReturnType,
ValidatorModelType,
Expand Down Expand Up @@ -259,73 +260,109 @@ class Validator {
}

/**
* 转换校验数据,支持对象和数组两种模式
* 转换数据为 map 结构,方便后续查找数据
* @param data
*/
private convertData = (data: ValidateDataModel): Array<ValidateDataModelItem> => {
if (isObject(data)) {
return Object.keys(data).map((name) => ({
name,
...(data as any)[name]
}))
}
return data as Array<ValidateDataModelItem>
}

private convertDataToMap = (data: Array<ValidateDataModelItem>) => {
private convertDataToMap(data: ValidateDataModel): ValidateContextType['dataKeyMap'] {
const dataKeyMap: ValidateContextType['dataKeyMap'] = new Map()
data.forEach((item) => {
dataKeyMap.set(item.dataKey, item)
!(item as ValidateDataModelMatrix).matrix && dataKeyMap.set(item.dataKey, item)
})
return dataKeyMap
}

/**
* 单个规则校验
* @param model
* @param data
* @param dataKeyMap
*/
private async singleValidate(
model: ValidateDataModelItem,
data: ValidateDataModel,
dataKeyMap: ValidateContextType['dataKeyMap']
) {
const fieldName = model.dataKey
const { value, ...rest } = model
const aliasName = model.label ?? fieldName
if ((model as ValidateDataModelMatrix).matrix) {
const { columns, data } = (model as ValidateDataModelMatrix).value
const errorResult: Record<string, string> = {}
for (let i = 0; i < data.length; i++) {
const item = data[i]
const matrixRowData = []
for (let j = 0; j < columns.length; j++) {
const column = columns[j]
matrixRowData.push({
...column,
rowId: item.rowId,
matrixId: (model as ValidateDataModelMatrix).matrixId,
value: item[column.dataKey]
})
}
try {
await this.validate(matrixRowData)
} catch (error) {
Object.assign(errorResult, error)
}
}
return errorResult
}
// 必填校验优先级最高
if (model.required && isEmpty(value)) {
return this.formatMessage(this.validateModel.get('required')!.message, aliasName)
}
// 如果当前数据存在局部校验规则,则不执行 rules 规则
if (rest.regexp || rest.validator) {
return this.scopeValidateHandler(value, rest as ScopeValidateType, {
data: data,
dataKeyMap
})
}
return await this.validateHandler(
value,
aliasName,
rest,
{ data: data, dataKeyMap }
)
}

/**
* 执行校验
* @param data 校验数据
* @param options
*/
validate = (data: ValidateDataModel, options?: {
validate(data: ValidateDataModel, options?: {
// 是否校验所有规则,默认:true,如果存在多个异步校验,不建议开启
checkAll?: boolean
}): ValidateReturnType => {
}): ValidateReturnType {
return new Promise((resolve, reject) => {
(async () => {
const { checkAll = true } = options ?? {}
const errorResult: Record<string, ValidateErrorType> = {}
const convertedData = this.convertData(data)
const dataKeyMap = this.convertDataToMap(convertedData)
for (let i = 0; i < convertedData.length; i++) {
const item = convertedData[i]
const fieldName = item.dataKey
const { value, ...rest } = item
const aliasName = item.label ?? fieldName
let result: ValidateErrorType | boolean
// 必填校验优先级最高
if (item.required && isEmpty(value)) {
result = this.formatMessage(this.validateModel.get('required')!.message, aliasName)
} else {
// 如果当前数据存在局部校验规则,则不执行 rules 规则
if (rest.regexp || rest.validator) {
result = await this.scopeValidateHandler(value, rest as ScopeValidateType, {
data: convertedData,
dataKeyMap
})
} else {
result = await this.validateHandler(
value,
aliasName,
rest,
{ data: convertedData, dataKeyMap }
)
}
}
const dataKeyMap = this.convertDataToMap(data)
for (let i = 0; i < data.length; i++) {
const model = data[i]
const result = await this.singleValidate(model, data, dataKeyMap)
if (this.isValidateFail(result)) {
const { matrixId, rowId } = (model as ValidateDataModelMatrix)
// 如果不是校验所有,遇见第一个错误则结束本次校验
if (!checkAll) {
reject({ [fieldName]: result })
reject({ [model.dataKey]: result })
}
if (matrixId) {
if (isObject(result)) {
Object.assign(errorResult, result)
} else {
Object.assign(errorResult, {
[`${matrixId}.${rowId}.${model.dataKey}`]: result as ValidateErrorType
})
}
} else {
Object.assign(errorResult, {
[model.dataKey]: result as ValidateErrorType
})
}
errorResult[fieldName] = result as ValidateErrorType
}
}
if (checkAll && isObject(errorResult) && !isEmpty(errorResult)) {
Expand Down
Loading

0 comments on commit 603556a

Please sign in to comment.