合作QA是大聪明?撸个接口校验工具保命(6)
一、 背景 & 前情回顾
本篇小作文主要讨论了以下两个问题:
-
RuleSet.prototype.doInterceptor 方法创建的拦截器为什么没有同步阻塞的执行校验逻辑?首先防止校验拦截器过早注册但是后续的修改拦截器对参数进行额外修改时获取不到最终的数据结果;另一方面就是这么做可以有更好的性能表现;
-
RuleSet.protoype.exec 方法的细则问题,主要工作就是校验方法:解析 config.url 和 config.method 备用;将公参
schema['*']
混入到各个接口的 schema 中;执行 this.testMethod 校验方法;深复制 config.param/data 到 finalParam 排除校验对源数据的影响;调用 this.traverse 进行参数参数校验
本文则接上文继续讨论 RuleSet.prototype.exec 中的其余方法调用;
二、RuleSet.prototype.testMethod
-
方法参数:
- u:url,即当前请求的后端接口;
- s:schema,当前请求接口对应的 schema 对象;
- m:method,当前请求使用 http method;
-
方法作用:判断 schema 中约束的 http method 和当前真实请求中的 http method 是否一致;
class RuleSet {
constructor (xfetch, schema) {}
exec (cfg) {
this.testMethod(api, apiSchema, method)
}
testMethod (u, s, m) {
// 检测 http 方法类型
if (s.method && s.method.toLowerCase() !== m.toLowerCase()) {
// 不满足就把错误收集起来
this.errors.push(genMethodErr(s, m))
}
}
}
- 举个例子: 下面这个例子就会校验 some/api/name 接口必须使用 GET 方法,如果使用其他方法就会抛出一个 方法类型错误 类型的错误,这个错误将会被收集到 this.errors 数组中
const schema = {
'/some/api/name': {
method: 'GET'
}
}
三、RuleSet.prototype.traverse
- 方法参数:
- api,当前需要校验的接口名字,例如 some/api/name
- schema,api 接口对应的 schema 对象;
- finalParam,最终需要校验的参数对象,这个参数对象是从 config.param 和 config.data 深复制得来的;
- 方法作用:for in 遍历 schema ,对接口参数进行校验,具体工作如下。
- 判断 key 是否是 finalParam 的私有属性,如果不是直接 continue 忽略当前 key 并进行下一个;
- 将简写的 type 方式 和 value 进行抹平;
- 拆分类型中的预处理器并简化类型;
- 从 finalParam 中取出 key 对应的参数值;
- 如果 schema 中存在 assert 函数,则优先通过 this.testAssert 方法执行 assert 断言函数;
- 接下来处理固定值的请情况,即类型传递了一个的字面量的值;
- 调用预处理器对参数进行处理;
- 调用 this.testType 对执行过预处理器的参数执行校验;
class RuleSet {
traverse (api, schema, finalParam) {
for (let key in schema) {
if (!schema.hasOwnProperty(key)) continue
let keySchema = schema[key]
let withoutTypeOrValue = ['type', 'value'].every((itm) => typeof keySchema[itm] === 'undefined')
schema[key] = withoutTypeOrValue && !keySchema.assert ? { type: keySchema } : keySchema
keySchema = schema[key]
// simplify type
this.splitTypeAndProcessor(keySchema)
let keyParam = finalParam[key]
// assert
if (keySchema.assert) {
// construct provide
let provide = {}
keySchema.provide?.forEach((k) => {
provide[k] = finalParam[k]
})
this.testAssert(api, keySchema, key, keyParam, provide)
continue
}
// 处理 value
if (this.getTypeOf(keySchema.value) !== BASIC_MAP.undefined) {
this.testValue(api, keySchema, key, keyParam)
continue
}
// preProcessor before type check
let processedParam = this.preProcessor(keySchema, keyParam)
// type check
this.testType(api, keySchema, key, processedParam)
}
}
}
4.1 为什么 for in schema?
到这里估计很多人都会好奇,为啥要 for in schema 而不是 for in finalParma?
这里有必要强调一个理念,我们前面设计 schema 的时候就提及过的:schema 存在则认定为 required,也就是说只要 schema 中声明的就是必须参数;
但是在很多接口中有些参数是可有可无的,这也就决定了 schema 应该是 finalParam 的一个子集,遍历 schema 的开销也会相对少一些。
基于以上考虑,我们决定遍历 schema 而非 finalParam!
4.2 为什么要抹平简写方式?
在回答为什么之前需要先来复习一下什么是简写方式。在我们的校验工具中,对 schema 对象的要求是某个参数名对应一个类型对象,当类型是个具体的值、正则、基本类型的时候可以缩写成简单方式,举个例子:
// 全写方式
const schema = {
'some/api/name': {
param1: {
type: 'string'
}
}
}
// 简写方式
const schema2 = {
'some/api/name': {
parma1: 'string'
}
}
以上两种方式是等价的,既然是等价的为啥还要设计一种复杂的方式呢?简写方式的设计很简单,就是为了减少开发成本,提升编写 schema 的效率;
复杂方式之所以存在是因为除了上面写的正则、字面量的值、基本类型之外的类型,比如接口、枚举值都是需要其他类型校验细则的,这些细则也必须存在于当前的类型对象中;
说完简写和全写的方式,接着回答为什么要抹平?
简写的方式纯属语法糖,后面的抹平是为了在工具层面可以提供一种同一的调用方式,经过处理的类型对象都是一个有 type 属性的对象,我们在后续的代码编写过程中就可以放心大胆的调用 schema[key].type 属性;
这种设计思路也是收到 webpack 的启发,我们在配置 loader 的时候可以有很多种写法,但是 webpack 内部在处理 loader 时则会对他们进行同一的抹平,不管你用的什么方式设置 loader,最终 webpack 拿到的都是经过标准化处理的结果。
四、总结
本篇小作文我们讨论 testMethod 和 travere 方法:
-
testMethod 方法用于校验 http method 是否和符合预期。基于这种路数,我们甚至可以扩展一种校验请求头信息的功能,当然如果你有兴趣可以尝试一下~
-
traverse 方法用于遍历 schema 对 finalParam 中的值进行校验;另外我么还讨论了几个细节问题:为啥遍历 schema 而不去遍历 finalParam、既然设计了简写方式为什么还要抹平简写方式 这两个问题;
转载自:https://juejin.cn/post/7136820660625473544