持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的27天,点击查看活动详情
一、 背景 & 前情回顾
上一篇小作文给大家介绍了一下这个工具诞生的背景,从网上搜了一下似乎没有啥好的方案,经过对比得到了这个工具技术方案,工具需要满足以下的要求:
-
校验参数类型,目前设计的是 schema 形式,只要 schema 中声明的参数都认定为必须传;
-
我们设计了一种 schema 的形式来解决类型声明的问题,另外设计了 assert 函数来解决参数间有关联的场景;
-
当检测到参数异常的时候需要设计异常处理的出口;
这一篇我们继续讨论工具的细节问题
二、 支持的类型
2.1 某个指定的值
当某个参数是固定值是,参数类型对象实例:
export default {
'some/api/name': {
'paramName': {
value: 'someVal '
}
}
}
或者简化成
export default {
'some/api/name': {
'paramName': 'someVal'
}
}
2.2 符合某个规则的字符串
通过正则约束,示例:
export default {
'some/api/name': {
'paramName': {
type: /\d+/g
}
}
}
2.3 基本类型的值
注意这个类型需要使用字面量的字符串,'string'、'number'、 'null'、 'undefined'、'boolean',
export default {
'some/api/name': {
'paramName': {
type: 'string'
}
}
}
此时参数的类型对象同样可以简写:
export default {
'some/api/name': {
'paramName': 'string'
}
}
2.4 基本类型的联合类型
例如,'string|number',支持简写 { paramName: 'string|number' };
2.5 枚举值:'enum'
须指定枚举范围,示例
export default {
'some/api/name': {
'paramName': {
type: 'enum',
enumRange: [28, 29]
}
}
}
2.6 interface
当要校验的参数值是一个 json 时使用,类型需要使用 interface= 开头,等号后紧跟interface 名字,接口的声明须在和 type 同级的实现。
接口是可以无限制嵌套的,例如下面的 gradeInterface 就是 aInterfaceName 的子级接口,当然了,如果这个接口是复用的,建议单独将这个接口抽离成一个对象;
例如:
{
paramName: {
type: 'interface=aInterfaceName',
aInterfaceName: {
name: 'string',
age: 'number',
grades: 'interface=gradeInterface', // 接口可以无限制嵌套
gradeInterface: { // 需要在使用的对象中声明这个接口
math: 'number',
chinese: 'number',
pe: 'number',
chemestry: 'number'
}
}
},
}
2.7 数组
数组分两种情况,精确类型数组和任意数组
-
精确类型数组,支持联合类型和 interface,例如
{ paramName: { type: 'array[string]' } }, -
任意数组,注意不是 'array[any]',例如:
{ paramName: { type: 'array' } }
export default {
'aaa/bbb': {
// 精确数组
testAry: {
type: 'array[number]'
},
// 也可以简写
testAry2: 'array[number]',
// 使用接口:
testAry3: {
type: 'array[interface=aryItemInterface]',
aryItemIterface: {
eid: 'string',
charge: 'string|number'
}
},
// 任意数组:
testAry4: 'array'
}
}
2.8 处理特殊的参数间联动的场景
在该场景下,不再需要指定 type,取而代之的是 assert 断言函数以及该参数需要联动的其他参数 provide;
assert 函数接收三个参数:
- param 是 当前要校验的参数名对应的值;
- ctx 是参数校验工具提供的上下文对象,包含辅助校验需要的工具函数等
- inject 则是下面 provide 指定的与 xx 联动的参数对象,key 是参数名,例如下面的 reactive_with_xxx,值就是其对应的值
这里为啥需要显示的透传 provide 指定当前参数需要与哪个参数进行联动呢?直接把整个参数对象都传过来不好吗?这么设计不是考虑便利性,而是考虑确定性,provide 显示声明,不管谁看都能迅速发现 multi_uire_duc 参数与那些参数发生了联动;
注意!在所有这些类型中,assert 拥有最高的的优先级,一旦发现类型对象中有 assert 函数存在,则忽略其他类型校验;
export default {
'aa/bb/cc/dd/ee': {
multi_uire_duc: {
assert (param, ctx, inject) {
let isString = ctx.getTypeOf(param) === 'String'
switch (+inject.urm_v1) {
case 0:
case 1:
return !param || (isString && param.length === 0)
case 2:
let pJson = JSON.parse(param)
return pJson.someValue ? true : '未通过校验!'
}
},
provide: ['urm_v1']
}
},
}
三、来一个 schema 示例
在给出具体的 schema 之前,要提议一种组织 schema 的方式:
首先声明的是公参 schema,公参 schema 的接口使用 '*' 作为接口,它对应的参数对象将会 mixin 到各个接口参数中;
其次,根据业务功能抽离剩下功能模块的 schema,例如下单和购物车用一个 schema 对象;
3.1 公参 schema
这个 schema 表示公参有:xx、ee、zzz 三个参数;
export default {
'*': {
xx: {
assert (param, ctx, inject) {
if (isInWhiteList(ctx.api)) return true
return ctx.getTypeOf(param) === 'String' ? true : 'xx is a CommonParam, please check'
}
},
ee: {
type: 'number'
},
zzz: {
type: 'enum',
enumRange: [28, 29]
}
}
}
3.2 某个业务模块的接口 schema
// 白名单列表,不推荐使用,但结合 assert 函数可以作为紧急出口
let commonParamsWhiteList = [
// 'xx/xx'
]
const isInWhiteList = k => commonParamsWhiteList.includes(k)
// schema 主体
export default {
'some/api/name': {
zzz: {
type: 'enum',
enumRange: [28, 29]
},
xxx: {
assert (param, ctx, inject) {
// assert 需要返回 true 表示通过校验,
// 其他返回值值均视为校验失败,并且作为异常信息抛出
return inject.reactive_with_xx ? true : 'reactive_with_xx 不存在'
},
// xxx 参数会和 reactive_with_xx 参数联动
provide: ['reactive_with_xx']
}
}
}
四、总结
本篇小作文详细讨论了这个接口校验工具支持的类型:
- 基本类型;
- 基本联合类型;
- 正则;
- 固定值;
- 接口;
- 数组,包含精确数组和任意数组,精确数组项直至以上所有类型;
- assert 函数,搭配
provide指定联动参数,可以解决参数互相联动的问题;
下一篇,我们开始讨论具体的实现;